workflow.rs

  1mod step_view;
  2
  3use crate::{
  4    prompts::StepResolutionContext, AssistantPanel, Context, InlineAssistId, InlineAssistant,
  5};
  6use anyhow::{anyhow, Error, Result};
  7use collections::HashMap;
  8use editor::Editor;
  9use futures::future;
 10use gpui::{
 11    Model, ModelContext, Task, UpdateGlobal as _, View, WeakModel, WeakView, WindowContext,
 12};
 13use language::{Anchor, Buffer, BufferSnapshot, SymbolPath};
 14use language_model::{LanguageModelRegistry, LanguageModelRequestMessage, Role};
 15use project::Project;
 16use rope::Point;
 17use serde::{Deserialize, Serialize};
 18use smol::stream::StreamExt;
 19use std::{cmp, fmt::Write, ops::Range, sync::Arc};
 20use text::{AnchorRangeExt as _, OffsetRangeExt as _};
 21use util::ResultExt as _;
 22use workspace::Workspace;
 23
 24pub use step_view::WorkflowStepView;
 25
 26pub struct WorkflowStep {
 27    context: WeakModel<Context>,
 28    context_buffer_range: Range<Anchor>,
 29    tool_output: String,
 30    resolve_task: Option<Task<()>>,
 31    pub resolution: Option<Result<WorkflowStepResolution, Arc<Error>>>,
 32}
 33
 34#[derive(Clone, Debug, Eq, PartialEq)]
 35pub struct WorkflowStepResolution {
 36    pub title: String,
 37    pub suggestion_groups: HashMap<Model<Buffer>, Vec<WorkflowSuggestionGroup>>,
 38}
 39
 40#[derive(Clone, Debug, Eq, PartialEq)]
 41pub struct WorkflowSuggestionGroup {
 42    pub context_range: Range<language::Anchor>,
 43    pub suggestions: Vec<WorkflowSuggestion>,
 44}
 45
 46#[derive(Clone, Debug, Eq, PartialEq)]
 47pub enum WorkflowSuggestion {
 48    Update {
 49        symbol_path: SymbolPath,
 50        range: Range<language::Anchor>,
 51        description: String,
 52    },
 53    CreateFile {
 54        description: String,
 55    },
 56    InsertSiblingBefore {
 57        symbol_path: SymbolPath,
 58        position: language::Anchor,
 59        description: String,
 60    },
 61    InsertSiblingAfter {
 62        symbol_path: SymbolPath,
 63        position: language::Anchor,
 64        description: String,
 65    },
 66    PrependChild {
 67        symbol_path: Option<SymbolPath>,
 68        position: language::Anchor,
 69        description: String,
 70    },
 71    AppendChild {
 72        symbol_path: Option<SymbolPath>,
 73        position: language::Anchor,
 74        description: String,
 75    },
 76    Delete {
 77        symbol_path: SymbolPath,
 78        range: Range<language::Anchor>,
 79    },
 80}
 81
 82impl WorkflowStep {
 83    pub fn new(range: Range<Anchor>, context: WeakModel<Context>) -> Self {
 84        Self {
 85            context_buffer_range: range,
 86            tool_output: String::new(),
 87            context,
 88            resolution: None,
 89            resolve_task: None,
 90        }
 91    }
 92
 93    pub fn resolve(&mut self, cx: &mut ModelContext<WorkflowStep>) -> Option<()> {
 94        let range = self.context_buffer_range.clone();
 95        let context = self.context.upgrade()?;
 96        let context = context.read(cx);
 97        let project = context.project()?;
 98        let prompt_builder = context.prompt_builder();
 99        let mut request = context.to_completion_request(cx);
100        let model = LanguageModelRegistry::read_global(cx).active_model();
101        let context_buffer = context.buffer();
102        let step_text = context_buffer
103            .read(cx)
104            .text_for_range(range.clone())
105            .collect::<String>();
106
107        let mut workflow_context = String::new();
108        for message in context.messages(cx) {
109            write!(&mut workflow_context, "<message role={}>", message.role).unwrap();
110            for chunk in context_buffer.read(cx).text_for_range(message.offset_range) {
111                write!(&mut workflow_context, "{chunk}").unwrap();
112            }
113            write!(&mut workflow_context, "</message>").unwrap();
114        }
115
116        self.resolve_task = Some(cx.spawn(|this, mut cx| async move {
117            let result = async {
118                let Some(model) = model else {
119                    return Err(anyhow!("no model selected"));
120                };
121
122                this.update(&mut cx, |this, cx| {
123                    this.tool_output.clear();
124                    this.resolution = None;
125                    this.result_updated(cx);
126                    cx.notify();
127                })?;
128
129                let resolution_context = StepResolutionContext {
130                    workflow_context,
131                    step_to_resolve: step_text.clone(),
132                };
133                let mut prompt =
134                    prompt_builder.generate_step_resolution_prompt(&resolution_context)?;
135                prompt.push_str(&step_text);
136                request.messages.push(LanguageModelRequestMessage {
137                    role: Role::User,
138                    content: vec![prompt.into()],
139                    cache: false,
140                });
141
142                // Invoke the model to get its edit suggestions for this workflow step.
143                let mut stream = model
144                    .use_tool_stream::<tool::WorkflowStepResolutionTool>(request, &cx)
145                    .await?;
146                while let Some(chunk) = stream.next().await {
147                    let chunk = chunk?;
148                    this.update(&mut cx, |this, cx| {
149                        this.tool_output.push_str(&chunk);
150                        cx.notify();
151                    })?;
152                }
153
154                let resolution = this.update(&mut cx, |this, _| {
155                    serde_json::from_str::<tool::WorkflowStepResolutionTool>(&this.tool_output)
156                })??;
157
158                this.update(&mut cx, |this, cx| {
159                    this.tool_output = serde_json::to_string_pretty(&resolution).unwrap();
160                    cx.notify();
161                })?;
162
163                // Translate the parsed suggestions to our internal types, which anchor the suggestions to locations in the code.
164                let suggestion_tasks: Vec<_> = resolution
165                    .suggestions
166                    .iter()
167                    .map(|suggestion| suggestion.resolve(project.clone(), cx.clone()))
168                    .collect();
169
170                // Expand the context ranges of each suggestion and group suggestions with overlapping context ranges.
171                let suggestions = future::join_all(suggestion_tasks)
172                    .await
173                    .into_iter()
174                    .filter_map(|task| task.log_err())
175                    .collect::<Vec<_>>();
176
177                let mut suggestions_by_buffer = HashMap::default();
178                for (buffer, suggestion) in suggestions {
179                    suggestions_by_buffer
180                        .entry(buffer)
181                        .or_insert_with(Vec::new)
182                        .push(suggestion);
183                }
184
185                let mut suggestion_groups_by_buffer = HashMap::default();
186                for (buffer, mut suggestions) in suggestions_by_buffer {
187                    let mut suggestion_groups = Vec::<WorkflowSuggestionGroup>::new();
188                    let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
189                    // Sort suggestions by their range so that earlier, larger ranges come first
190                    suggestions.sort_by(|a, b| a.range().cmp(&b.range(), &snapshot));
191
192                    // Merge overlapping suggestions
193                    suggestions.dedup_by(|a, b| b.try_merge(a, &snapshot));
194
195                    // Create context ranges for each suggestion
196                    for suggestion in suggestions {
197                        let context_range = {
198                            let suggestion_point_range = suggestion.range().to_point(&snapshot);
199                            let start_row = suggestion_point_range.start.row.saturating_sub(5);
200                            let end_row = cmp::min(
201                                suggestion_point_range.end.row + 5,
202                                snapshot.max_point().row,
203                            );
204                            let start = snapshot.anchor_before(Point::new(start_row, 0));
205                            let end = snapshot
206                                .anchor_after(Point::new(end_row, snapshot.line_len(end_row)));
207                            start..end
208                        };
209
210                        if let Some(last_group) = suggestion_groups.last_mut() {
211                            if last_group
212                                .context_range
213                                .end
214                                .cmp(&context_range.start, &snapshot)
215                                .is_ge()
216                            {
217                                // Merge with the previous group if context ranges overlap
218                                last_group.context_range.end = context_range.end;
219                                last_group.suggestions.push(suggestion);
220                            } else {
221                                // Create a new group
222                                suggestion_groups.push(WorkflowSuggestionGroup {
223                                    context_range,
224                                    suggestions: vec![suggestion],
225                                });
226                            }
227                        } else {
228                            // Create the first group
229                            suggestion_groups.push(WorkflowSuggestionGroup {
230                                context_range,
231                                suggestions: vec![suggestion],
232                            });
233                        }
234                    }
235
236                    suggestion_groups_by_buffer.insert(buffer, suggestion_groups);
237                }
238
239                Ok((resolution.step_title, suggestion_groups_by_buffer))
240            };
241
242            let result = result.await;
243            this.update(&mut cx, |this, cx| {
244                this.resolution = Some(match result {
245                    Ok((title, suggestion_groups)) => Ok(WorkflowStepResolution {
246                        title,
247                        suggestion_groups,
248                    }),
249                    Err(error) => Err(Arc::new(error)),
250                });
251                this.context
252                    .update(cx, |context, cx| context.workflow_step_updated(range, cx))
253                    .ok();
254                cx.notify();
255            })
256            .ok();
257        }));
258        None
259    }
260
261    fn result_updated(&mut self, cx: &mut ModelContext<Self>) {
262        self.context
263            .update(cx, |context, cx| {
264                context.workflow_step_updated(self.context_buffer_range.clone(), cx)
265            })
266            .ok();
267    }
268}
269
270impl WorkflowSuggestion {
271    pub fn range(&self) -> Range<language::Anchor> {
272        match self {
273            Self::Update { range, .. } => range.clone(),
274            Self::CreateFile { .. } => language::Anchor::MIN..language::Anchor::MAX,
275            Self::InsertSiblingBefore { position, .. }
276            | Self::InsertSiblingAfter { position, .. }
277            | Self::PrependChild { position, .. }
278            | Self::AppendChild { position, .. } => *position..*position,
279            Self::Delete { range, .. } => range.clone(),
280        }
281    }
282
283    pub fn description(&self) -> Option<&str> {
284        match self {
285            Self::Update { description, .. }
286            | Self::CreateFile { description }
287            | Self::InsertSiblingBefore { description, .. }
288            | Self::InsertSiblingAfter { description, .. }
289            | Self::PrependChild { description, .. }
290            | Self::AppendChild { description, .. } => Some(description),
291            Self::Delete { .. } => None,
292        }
293    }
294
295    fn description_mut(&mut self) -> Option<&mut String> {
296        match self {
297            Self::Update { description, .. }
298            | Self::CreateFile { description }
299            | Self::InsertSiblingBefore { description, .. }
300            | Self::InsertSiblingAfter { description, .. }
301            | Self::PrependChild { description, .. }
302            | Self::AppendChild { description, .. } => Some(description),
303            Self::Delete { .. } => None,
304        }
305    }
306
307    fn symbol_path(&self) -> Option<&SymbolPath> {
308        match self {
309            Self::Update { symbol_path, .. } => Some(symbol_path),
310            Self::InsertSiblingBefore { symbol_path, .. } => Some(symbol_path),
311            Self::InsertSiblingAfter { symbol_path, .. } => Some(symbol_path),
312            Self::PrependChild { symbol_path, .. } => symbol_path.as_ref(),
313            Self::AppendChild { symbol_path, .. } => symbol_path.as_ref(),
314            Self::Delete { symbol_path, .. } => Some(symbol_path),
315            Self::CreateFile { .. } => None,
316        }
317    }
318
319    fn kind(&self) -> &str {
320        match self {
321            Self::Update { .. } => "Update",
322            Self::CreateFile { .. } => "CreateFile",
323            Self::InsertSiblingBefore { .. } => "InsertSiblingBefore",
324            Self::InsertSiblingAfter { .. } => "InsertSiblingAfter",
325            Self::PrependChild { .. } => "PrependChild",
326            Self::AppendChild { .. } => "AppendChild",
327            Self::Delete { .. } => "Delete",
328        }
329    }
330
331    fn try_merge(&mut self, other: &Self, buffer: &BufferSnapshot) -> bool {
332        let range = self.range();
333        let other_range = other.range();
334
335        // Don't merge if we don't contain the other suggestion.
336        if range.start.cmp(&other_range.start, buffer).is_gt()
337            || range.end.cmp(&other_range.end, buffer).is_lt()
338        {
339            return false;
340        }
341
342        if let Some(description) = self.description_mut() {
343            if let Some(other_description) = other.description() {
344                description.push('\n');
345                description.push_str(other_description);
346            }
347        }
348        true
349    }
350
351    pub fn show(
352        &self,
353        editor: &View<Editor>,
354        excerpt_id: editor::ExcerptId,
355        workspace: &WeakView<Workspace>,
356        assistant_panel: &View<AssistantPanel>,
357        cx: &mut WindowContext,
358    ) -> Option<InlineAssistId> {
359        let mut initial_transaction_id = None;
360        let initial_prompt;
361        let suggestion_range;
362        let buffer = editor.read(cx).buffer().clone();
363        let snapshot = buffer.read(cx).snapshot(cx);
364
365        match self {
366            Self::Update {
367                range, description, ..
368            } => {
369                initial_prompt = description.clone();
370                suggestion_range = snapshot.anchor_in_excerpt(excerpt_id, range.start)?
371                    ..snapshot.anchor_in_excerpt(excerpt_id, range.end)?;
372            }
373            Self::CreateFile { description } => {
374                initial_prompt = description.clone();
375                suggestion_range = editor::Anchor::min()..editor::Anchor::min();
376            }
377            Self::InsertSiblingBefore {
378                position,
379                description,
380                ..
381            } => {
382                let position = snapshot.anchor_in_excerpt(excerpt_id, *position)?;
383                initial_prompt = description.clone();
384                suggestion_range = buffer.update(cx, |buffer, cx| {
385                    buffer.start_transaction(cx);
386                    let line_start = buffer.insert_empty_line(position, true, true, cx);
387                    initial_transaction_id = buffer.end_transaction(cx);
388                    buffer.refresh_preview(cx);
389
390                    let line_start = buffer.read(cx).anchor_before(line_start);
391                    line_start..line_start
392                });
393            }
394            Self::InsertSiblingAfter {
395                position,
396                description,
397                ..
398            } => {
399                let position = snapshot.anchor_in_excerpt(excerpt_id, *position)?;
400                initial_prompt = description.clone();
401                suggestion_range = buffer.update(cx, |buffer, cx| {
402                    buffer.start_transaction(cx);
403                    let line_start = buffer.insert_empty_line(position, true, true, cx);
404                    initial_transaction_id = buffer.end_transaction(cx);
405                    buffer.refresh_preview(cx);
406
407                    let line_start = buffer.read(cx).anchor_before(line_start);
408                    line_start..line_start
409                });
410            }
411            Self::PrependChild {
412                position,
413                description,
414                ..
415            } => {
416                let position = snapshot.anchor_in_excerpt(excerpt_id, *position)?;
417                initial_prompt = description.clone();
418                suggestion_range = buffer.update(cx, |buffer, cx| {
419                    buffer.start_transaction(cx);
420                    let line_start = buffer.insert_empty_line(position, false, true, cx);
421                    initial_transaction_id = buffer.end_transaction(cx);
422                    buffer.refresh_preview(cx);
423
424                    let line_start = buffer.read(cx).anchor_before(line_start);
425                    line_start..line_start
426                });
427            }
428            Self::AppendChild {
429                position,
430                description,
431                ..
432            } => {
433                let position = snapshot.anchor_in_excerpt(excerpt_id, *position)?;
434                initial_prompt = description.clone();
435                suggestion_range = buffer.update(cx, |buffer, cx| {
436                    buffer.start_transaction(cx);
437                    let line_start = buffer.insert_empty_line(position, true, false, cx);
438                    initial_transaction_id = buffer.end_transaction(cx);
439                    buffer.refresh_preview(cx);
440
441                    let line_start = buffer.read(cx).anchor_before(line_start);
442                    line_start..line_start
443                });
444            }
445            Self::Delete { range, .. } => {
446                initial_prompt = "Delete".to_string();
447                suggestion_range = snapshot.anchor_in_excerpt(excerpt_id, range.start)?
448                    ..snapshot.anchor_in_excerpt(excerpt_id, range.end)?;
449            }
450        }
451
452        InlineAssistant::update_global(cx, |inline_assistant, cx| {
453            Some(inline_assistant.suggest_assist(
454                editor,
455                suggestion_range,
456                initial_prompt,
457                initial_transaction_id,
458                Some(workspace.clone()),
459                Some(assistant_panel),
460                cx,
461            ))
462        })
463    }
464}
465
466pub mod tool {
467    use super::*;
468    use anyhow::Context as _;
469    use gpui::AsyncAppContext;
470    use language::ParseStatus;
471    use language_model::LanguageModelTool;
472    use project::ProjectPath;
473    use schemars::JsonSchema;
474    use std::path::Path;
475
476    #[derive(Debug, Serialize, Deserialize, JsonSchema)]
477    pub struct WorkflowStepResolutionTool {
478        /// An extremely short title for the edit step represented by these operations.
479        pub step_title: String,
480        /// A sequence of operations to apply to the codebase.
481        /// When multiple operations are required for a step, be sure to include multiple operations in this list.
482        pub suggestions: Vec<WorkflowSuggestionTool>,
483    }
484
485    impl LanguageModelTool for WorkflowStepResolutionTool {
486        fn name() -> String {
487            "edit".into()
488        }
489
490        fn description() -> String {
491            "suggest edits to one or more locations in the codebase".into()
492        }
493    }
494
495    /// A description of an operation to apply to one location in the codebase.
496    ///
497    /// This object represents a single edit operation that can be performed on a specific file
498    /// in the codebase. It encapsulates both the location (file path) and the nature of the
499    /// edit to be made.
500    ///
501    /// # Fields
502    ///
503    /// * `path`: A string representing the file path where the edit operation should be applied.
504    ///           This path is relative to the root of the project or repository.
505    ///
506    /// * `kind`: An enum representing the specific type of edit operation to be performed.
507    ///
508    /// # Usage
509    ///
510    /// `EditOperation` is used within a code editor to represent and apply
511    /// programmatic changes to source code. It provides a structured way to describe
512    /// edits for features like refactoring tools or AI-assisted coding suggestions.
513    #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
514    pub struct WorkflowSuggestionTool {
515        /// The path to the file containing the relevant operation
516        pub path: String,
517        #[serde(flatten)]
518        pub kind: WorkflowSuggestionToolKind,
519    }
520
521    impl WorkflowSuggestionTool {
522        pub(super) async fn resolve(
523            &self,
524            project: Model<Project>,
525            mut cx: AsyncAppContext,
526        ) -> Result<(Model<Buffer>, super::WorkflowSuggestion)> {
527            let path = self.path.clone();
528            let kind = self.kind.clone();
529            let buffer = project
530                .update(&mut cx, |project, cx| {
531                    let project_path = project
532                        .find_project_path(Path::new(&path), cx)
533                        .or_else(|| {
534                            // If we couldn't find a project path for it, put it in the active worktree
535                            // so that when we create the buffer, it can be saved.
536                            let worktree = project
537                                .active_entry()
538                                .and_then(|entry_id| project.worktree_for_entry(entry_id, cx))
539                                .or_else(|| project.worktrees(cx).next())?;
540                            let worktree = worktree.read(cx);
541
542                            Some(ProjectPath {
543                                worktree_id: worktree.id(),
544                                path: Arc::from(Path::new(&path)),
545                            })
546                        })
547                        .with_context(|| format!("worktree not found for {:?}", path))?;
548                    anyhow::Ok(project.open_buffer(project_path, cx))
549                })??
550                .await?;
551
552            let mut parse_status = buffer.read_with(&cx, |buffer, _cx| buffer.parse_status())?;
553            while *parse_status.borrow() != ParseStatus::Idle {
554                parse_status.changed().await?;
555            }
556
557            let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
558            let outline = snapshot.outline(None).context("no outline for buffer")?;
559
560            let suggestion = match kind {
561                WorkflowSuggestionToolKind::Update {
562                    symbol,
563                    description,
564                } => {
565                    let (symbol_path, symbol) = outline
566                        .find_most_similar(&symbol)
567                        .with_context(|| format!("symbol not found: {:?}", symbol))?;
568                    let symbol = symbol.to_point(&snapshot);
569                    let start = symbol
570                        .annotation_range
571                        .map_or(symbol.range.start, |range| range.start);
572                    let start = Point::new(start.row, 0);
573                    let end = Point::new(
574                        symbol.range.end.row,
575                        snapshot.line_len(symbol.range.end.row),
576                    );
577                    let range = snapshot.anchor_before(start)..snapshot.anchor_after(end);
578                    WorkflowSuggestion::Update {
579                        range,
580                        description,
581                        symbol_path,
582                    }
583                }
584                WorkflowSuggestionToolKind::Create { description } => {
585                    WorkflowSuggestion::CreateFile { description }
586                }
587                WorkflowSuggestionToolKind::InsertSiblingBefore {
588                    symbol,
589                    description,
590                } => {
591                    let (symbol_path, symbol) = outline
592                        .find_most_similar(&symbol)
593                        .with_context(|| format!("symbol not found: {:?}", symbol))?;
594                    let symbol = symbol.to_point(&snapshot);
595                    let position = snapshot.anchor_before(
596                        symbol
597                            .annotation_range
598                            .map_or(symbol.range.start, |annotation_range| {
599                                annotation_range.start
600                            }),
601                    );
602                    WorkflowSuggestion::InsertSiblingBefore {
603                        position,
604                        description,
605                        symbol_path,
606                    }
607                }
608                WorkflowSuggestionToolKind::InsertSiblingAfter {
609                    symbol,
610                    description,
611                } => {
612                    let (symbol_path, symbol) = outline
613                        .find_most_similar(&symbol)
614                        .with_context(|| format!("symbol not found: {:?}", symbol))?;
615                    let symbol = symbol.to_point(&snapshot);
616                    let position = snapshot.anchor_after(symbol.range.end);
617                    WorkflowSuggestion::InsertSiblingAfter {
618                        position,
619                        description,
620                        symbol_path,
621                    }
622                }
623                WorkflowSuggestionToolKind::PrependChild {
624                    symbol,
625                    description,
626                } => {
627                    if let Some(symbol) = symbol {
628                        let (symbol_path, symbol) = outline
629                            .find_most_similar(&symbol)
630                            .with_context(|| format!("symbol not found: {:?}", symbol))?;
631                        let symbol = symbol.to_point(&snapshot);
632
633                        let position = snapshot.anchor_after(
634                            symbol
635                                .body_range
636                                .map_or(symbol.range.start, |body_range| body_range.start),
637                        );
638                        WorkflowSuggestion::PrependChild {
639                            position,
640                            description,
641                            symbol_path: Some(symbol_path),
642                        }
643                    } else {
644                        WorkflowSuggestion::PrependChild {
645                            position: language::Anchor::MIN,
646                            description,
647                            symbol_path: None,
648                        }
649                    }
650                }
651                WorkflowSuggestionToolKind::AppendChild {
652                    symbol,
653                    description,
654                } => {
655                    if let Some(symbol) = symbol {
656                        let (symbol_path, symbol) = outline
657                            .find_most_similar(&symbol)
658                            .with_context(|| format!("symbol not found: {:?}", symbol))?;
659                        let symbol = symbol.to_point(&snapshot);
660
661                        let position = snapshot.anchor_before(
662                            symbol
663                                .body_range
664                                .map_or(symbol.range.end, |body_range| body_range.end),
665                        );
666                        WorkflowSuggestion::AppendChild {
667                            position,
668                            description,
669                            symbol_path: Some(symbol_path),
670                        }
671                    } else {
672                        WorkflowSuggestion::PrependChild {
673                            position: language::Anchor::MAX,
674                            description,
675                            symbol_path: None,
676                        }
677                    }
678                }
679                WorkflowSuggestionToolKind::Delete { symbol } => {
680                    let (symbol_path, symbol) = outline
681                        .find_most_similar(&symbol)
682                        .with_context(|| format!("symbol not found: {:?}", symbol))?;
683                    let symbol = symbol.to_point(&snapshot);
684                    let start = symbol
685                        .annotation_range
686                        .map_or(symbol.range.start, |range| range.start);
687                    let start = Point::new(start.row, 0);
688                    let end = Point::new(
689                        symbol.range.end.row,
690                        snapshot.line_len(symbol.range.end.row),
691                    );
692                    let range = snapshot.anchor_before(start)..snapshot.anchor_after(end);
693                    WorkflowSuggestion::Delete { range, symbol_path }
694                }
695            };
696
697            Ok((buffer, suggestion))
698        }
699    }
700
701    #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
702    #[serde(tag = "kind")]
703    pub enum WorkflowSuggestionToolKind {
704        /// Rewrites the specified symbol entirely based on the given description.
705        /// This operation completely replaces the existing symbol with new content.
706        Update {
707            /// A fully-qualified reference to the symbol, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`.
708            /// The path should uniquely identify the symbol within the containing file.
709            symbol: String,
710            /// A brief description of the transformation to apply to the symbol.
711            description: String,
712        },
713        /// Creates a new file with the given path based on the provided description.
714        /// This operation adds a new file to the codebase.
715        Create {
716            /// A brief description of the file to be created.
717            description: String,
718        },
719        /// Inserts a new symbol based on the given description before the specified symbol.
720        /// This operation adds new content immediately preceding an existing symbol.
721        InsertSiblingBefore {
722            /// A fully-qualified reference to the symbol, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`.
723            /// The new content will be inserted immediately before this symbol.
724            symbol: String,
725            /// A brief description of the new symbol to be inserted.
726            description: String,
727        },
728        /// Inserts a new symbol based on the given description after the specified symbol.
729        /// This operation adds new content immediately following an existing symbol.
730        InsertSiblingAfter {
731            /// A fully-qualified reference to the symbol, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`.
732            /// The new content will be inserted immediately after this symbol.
733            symbol: String,
734            /// A brief description of the new symbol to be inserted.
735            description: String,
736        },
737        /// Inserts a new symbol as a child of the specified symbol at the start.
738        /// This operation adds new content as the first child of an existing symbol (or file if no symbol is provided).
739        PrependChild {
740            /// An optional fully-qualified reference to the symbol after the code you want to insert, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`.
741            /// If provided, the new content will be inserted as the first child of this symbol.
742            /// If not provided, the new content will be inserted at the top of the file.
743            symbol: Option<String>,
744            /// A brief description of the new symbol to be inserted.
745            description: String,
746        },
747        /// Inserts a new symbol as a child of the specified symbol at the end.
748        /// This operation adds new content as the last child of an existing symbol (or file if no symbol is provided).
749        AppendChild {
750            /// An optional fully-qualified reference to the symbol before the code you want to insert, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`.
751            /// If provided, the new content will be inserted as the last child of this symbol.
752            /// If not provided, the new content will be applied at the bottom of the file.
753            symbol: Option<String>,
754            /// A brief description of the new symbol to be inserted.
755            description: String,
756        },
757        /// Deletes the specified symbol from the containing file.
758        Delete {
759            /// An fully-qualified reference to the symbol to be deleted, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`.
760            symbol: String,
761        },
762    }
763}