Make /auto print out the actions it wants to run

This commit is contained in:
Richard Feldman
2024-07-05 13:15:58 -04:00
parent c129e3b668
commit 481a554923

View File

@@ -1,11 +1,10 @@
use super::create_label_for_command;
use super::{SlashCommand, SlashCommandOutput};
use crate::{CompletionProvider, LanguageModelRequest, LanguageModelRequestMessage, Role};
use anyhow::anyhow;
use anyhow::Result;
use futures::FutureExt;
use anyhow::{anyhow, Result};
use futures::StreamExt;
use gpui::{AppContext, Task, WeakView};
use language::LspAdapterDelegate;
use language::{CodeLabel, LspAdapterDelegate};
use std::sync::{atomic::AtomicBool, Arc};
use ui::WindowContext;
use workspace::Workspace;
@@ -25,32 +24,42 @@ impl SlashCommand for AutoCommand {
"Automatically Infer Context".into()
}
fn requires_argument(&self) -> bool {
false
fn label(&self, cx: &AppContext) -> CodeLabel {
create_label_for_command("auto", &["--prompt"], cx)
}
fn complete_argument(
self: Arc<Self>,
_query: String,
_cancel: Arc<AtomicBool>,
_cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<String>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
// There's no autocomplete for a prompt, since it's arbitrary text.
Task::ready(Ok(Vec::new()))
}
fn requires_argument(&self) -> bool {
true
}
fn run(
self: Arc<Self>,
_argument: Option<&str>,
argument: Option<&str>,
_workspace: WeakView<Workspace>,
_delegate: Arc<dyn LspAdapterDelegate>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
let Some(argument) = argument else {
return Task::ready(Err(anyhow!("missing prompt")));
};
let prompt = format!("{PROMPT_INSTRUCTIONS_BEFORE_SUMMARY}\n{SUMMARY}\n{PROMPT_INSTRUCTIONS_AFTER_SUMMARY}\n{argument}");
let request = LanguageModelRequest {
model: CompletionProvider::global(cx).model(),
messages: vec![LanguageModelRequestMessage {
role: Role::User,
content: "please tell me a story".into(),
content: prompt,
}],
stop: vec![],
temperature: 1.0,
@@ -58,26 +67,117 @@ impl SlashCommand for AutoCommand {
let stream = CompletionProvider::global(cx).complete(request);
cx.spawn(|_cx| async move {
println!("/auto {argument}");
let mut wip_action: String = String::new();
let task = cx.spawn(|_cx| async move {
let stream_completion = async {
let mut messages = stream.await?;
while let Some(message) = messages.next().await {
let text = message?;
dbg!(&text);
chunked_line(&mut wip_action, &text, |line| {
println!("Running action: /{line}")
});
smol::future::yield_now().await;
}
println!("End of actions.");
anyhow::Ok(())
};
let result = stream_completion.await;
dbg!(&result);
stream_completion.await
});
Task::ready(Ok(SlashCommandOutput::default()))
cx.background_executor().spawn(async move {
task.await?;
Ok(SlashCommandOutput::default())
})
}
}
const PROMPT_INSTRUCTIONS_BEFORE_SUMMARY: &str = r#"
I'm going to give you a prompt. I don't want you to respond
to the prompt itself. I want you to figure out which of the following
actions on my project, if any, would help you answer the prompt.
Here are the actions:
## file
This action's parameter is a file path to one of the files
in the project. If you ask for this action, I will tell you
the full contents of the file, so you can learn all the
details of the file.
## search
This action's parameter is a string to search for across
the project. It will tell you which files this string
(or similar strings; it is a semantic search) appear in,
as well as some context of the lines surrounding each result.
---
That was the end of the list of actions.
Here is an XML summary of each of the files in my project:
"#;
const PROMPT_INSTRUCTIONS_AFTER_SUMMARY: &str = r#"
Actions have a cost, so only include actions that you think
will be helpful to you in doing a great job answering the
prompt in the future.
You must respond ONLY with a list of actions you would like to
perform. Each action should be on its own line, and followed by a space and then its parameter.
Actions can be performed more than once with different parameters.
Here is an example valid response:
```
file path/to/my/file.txt
file path/to/another/file.txt
search something to search for
search something else to search for
```
Once again, do not forget: you must respond ONLY in the format of
one action per line, and the action name should be followed by
its parameter. Your response must not include anything other
than a list of actions, with one action per line, in this format.
It is extremely important that you do not deviate from this format even slightly!
This is the end of my instructions for how to respond. The rest is the prompt:
"#;
const SUMMARY: &str = "";
fn chunked_line(wip: &mut String, chunk: &str, mut on_line_end: impl FnMut(&str)) {
// The first iteration of the loop should just push to wip
// and nothing else. We only push what we encountered in
// previous iterations of the loop.
//
// This correctly handles both the scenario where no
// newlines are encountered (the loop will only run once,
// and so will only push to wip), as well as the scenario
// where the chunk contains at least one newline but
// does not end in a newline (the last iteration of the
// loop will update wip but will not run anything).
let mut is_first_iteration = true;
for line in chunk.split('\n') {
if is_first_iteration {
is_first_iteration = false;
} else {
// Since this isn't the first iteration of the loop, we definitely hit a newline
// at the end of the previous iteration! Run the function on whatever wip we have.
on_line_end(wip);
wip.clear();
}
wip.push_str(line);
}
}