Recipe slash command parsing (#6173)
This commit is contained in:
@@ -797,7 +797,15 @@ impl Agent {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
match command_result {
|
match command_result {
|
||||||
Some(response) if response.role == rmcp::model::Role::Assistant => {
|
Err(e) => {
|
||||||
|
let error_message = Message::assistant()
|
||||||
|
.with_text(e.to_string())
|
||||||
|
.with_visibility(true, false);
|
||||||
|
return Ok(Box::pin(stream::once(async move {
|
||||||
|
Ok(AgentEvent::Message(error_message))
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
Ok(Some(response)) if response.role == rmcp::model::Role::Assistant => {
|
||||||
SessionManager::add_message(
|
SessionManager::add_message(
|
||||||
&session_config.id,
|
&session_config.id,
|
||||||
&user_message.clone().with_visibility(true, false),
|
&user_message.clone().with_visibility(true, false),
|
||||||
@@ -830,7 +838,7 @@ impl Agent {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
Some(resolved_message) => {
|
Ok(Some(resolved_message)) => {
|
||||||
SessionManager::add_message(
|
SessionManager::add_message(
|
||||||
&session_config.id,
|
&session_config.id,
|
||||||
&user_message.clone().with_visibility(true, false),
|
&user_message.clone().with_visibility(true, false),
|
||||||
@@ -842,7 +850,7 @@ impl Agent {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
None => {
|
Ok(None) => {
|
||||||
SessionManager::add_message(&session_config.id, &user_message).await?;
|
SessionManager::add_message(&session_config.id, &user_message).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,11 @@ pub fn list_commands() -> &'static [CommandDef] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Agent {
|
impl Agent {
|
||||||
pub async fn execute_command(&self, message_text: &str, session_id: &str) -> Option<Message> {
|
pub async fn execute_command(
|
||||||
|
&self,
|
||||||
|
message_text: &str,
|
||||||
|
session_id: &str,
|
||||||
|
) -> Result<Option<Message>> {
|
||||||
let mut trimmed = message_text.trim().to_string();
|
let mut trimmed = message_text.trim().to_string();
|
||||||
|
|
||||||
if COMPACT_TRIGGERS.contains(&trimmed.as_str()) {
|
if COMPACT_TRIGGERS.contains(&trimmed.as_str()) {
|
||||||
@@ -49,37 +53,30 @@ impl Agent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !trimmed.starts_with('/') {
|
if !trimmed.starts_with('/') {
|
||||||
return None;
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let command_str = trimmed.strip_prefix('/').unwrap_or(&trimmed);
|
let command_str = trimmed.strip_prefix('/').unwrap_or(&trimmed);
|
||||||
let (command, params) = command_str
|
let (command, params_str) = command_str
|
||||||
.split_once(' ')
|
.split_once(' ')
|
||||||
.map(|(cmd, p)| (cmd, p.trim()))
|
.map(|(cmd, p)| (cmd, p.trim()))
|
||||||
.unwrap_or((command_str, ""));
|
.unwrap_or((command_str, ""));
|
||||||
|
|
||||||
let params: Vec<&str> = if params.is_empty() {
|
let params: Vec<&str> = if params_str.is_empty() {
|
||||||
vec![]
|
vec![]
|
||||||
} else {
|
} else {
|
||||||
params.split_whitespace().collect()
|
params_str.split_whitespace().collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = match command {
|
match command {
|
||||||
"prompts" => self.handle_prompts_command(¶ms, session_id).await,
|
"prompts" => self.handle_prompts_command(¶ms, session_id).await,
|
||||||
"prompt" => self.handle_prompt_command(¶ms, session_id).await,
|
"prompt" => self.handle_prompt_command(¶ms, session_id).await,
|
||||||
"compact" => self.handle_compact_command(session_id).await,
|
"compact" => self.handle_compact_command(session_id).await,
|
||||||
"clear" => self.handle_clear_command(session_id).await,
|
"clear" => self.handle_clear_command(session_id).await,
|
||||||
_ => {
|
_ => {
|
||||||
self.handle_recipe_command(command, ¶ms, session_id)
|
self.handle_recipe_command(command, params_str, session_id)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(msg) => msg,
|
|
||||||
Err(e) => {
|
|
||||||
Some(Message::assistant().with_text(format!("Error executing /{}: {}", command, e)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +261,7 @@ impl Agent {
|
|||||||
async fn handle_recipe_command(
|
async fn handle_recipe_command(
|
||||||
&self,
|
&self,
|
||||||
command: &str,
|
command: &str,
|
||||||
params: &[&str],
|
params_str: &str,
|
||||||
_session_id: &str,
|
_session_id: &str,
|
||||||
) -> Result<Option<Message>> {
|
) -> Result<Option<Message>> {
|
||||||
let full_command = format!("/{}", command);
|
let full_command = format!("/{}", command);
|
||||||
@@ -284,7 +281,64 @@ impl Agent {
|
|||||||
.parent()
|
.parent()
|
||||||
.ok_or_else(|| anyhow!("Recipe path has no parent directory"))?;
|
.ok_or_else(|| anyhow!("Recipe path has no parent directory"))?;
|
||||||
|
|
||||||
let param_values: Vec<String> = params.iter().map(|s| s.to_string()).collect();
|
let recipe_dir_str = recipe_dir.display().to_string();
|
||||||
|
let validation_result =
|
||||||
|
crate::recipe::validate_recipe::validate_recipe_template_from_content(
|
||||||
|
&recipe_content,
|
||||||
|
Some(recipe_dir_str),
|
||||||
|
)
|
||||||
|
.map_err(|e| anyhow!("Failed to parse recipe: {}", e))?;
|
||||||
|
|
||||||
|
let param_values: Vec<String> = if params_str.is_empty() {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
let params_without_default = validation_result
|
||||||
|
.parameters
|
||||||
|
.as_ref()
|
||||||
|
.map(|params| params.iter().filter(|p| p.default.is_none()).count())
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
if params_without_default <= 1 {
|
||||||
|
vec![params_str.to_string()]
|
||||||
|
} else {
|
||||||
|
let param_names: Vec<String> = validation_result
|
||||||
|
.parameters
|
||||||
|
.as_ref()
|
||||||
|
.map(|params| {
|
||||||
|
params
|
||||||
|
.iter()
|
||||||
|
.filter(|p| p.default.is_none())
|
||||||
|
.map(|p| p.key.clone())
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let error_message = format!(
|
||||||
|
"The /{} recipe requires {} parameters: {}.\n\n\
|
||||||
|
Slash command recipes only support 1 parameter.\n\n\
|
||||||
|
**To use this recipe:**\n\
|
||||||
|
• **CLI:** `goose run --recipe {} {}`\n\
|
||||||
|
• **Desktop:** Launch from the recipes sidebar to fill in parameters",
|
||||||
|
command,
|
||||||
|
params_without_default,
|
||||||
|
param_names
|
||||||
|
.iter()
|
||||||
|
.map(|name| format!("**{}**", name))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", "),
|
||||||
|
command,
|
||||||
|
param_names
|
||||||
|
.iter()
|
||||||
|
.map(|name| format!("--params {}=\"...\"", name))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ")
|
||||||
|
);
|
||||||
|
|
||||||
|
return Err(anyhow!(error_message));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let param_values_len = param_values.len();
|
||||||
|
|
||||||
let recipe = match build_recipe_from_template_with_positional_params(
|
let recipe = match build_recipe_from_template_with_positional_params(
|
||||||
recipe_content,
|
recipe_content,
|
||||||
@@ -298,7 +352,7 @@ impl Agent {
|
|||||||
"Recipe requires {} parameter(s): {}. Provided: {}",
|
"Recipe requires {} parameter(s): {}. Provided: {}",
|
||||||
parameters.len(),
|
parameters.len(),
|
||||||
parameters.join(", "),
|
parameters.join(", "),
|
||||||
params.len()
|
param_values_len
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
Err(e) => return Err(anyhow!("Failed to build recipe: {}", e)),
|
Err(e) => return Err(anyhow!("Failed to build recipe: {}", e)),
|
||||||
|
|||||||
Reference in New Issue
Block a user