feat: add mode completion (#1506)

This commit is contained in:
Wendy Tang
2025-03-04 13:56:09 -08:00
committed by GitHub
parent 389e8059f4
commit e30f50a92e
3 changed files with 91 additions and 9 deletions
@@ -75,6 +75,46 @@ impl GooseCompleter {
Ok((line.len(), vec![]))
}
/// Complete flags for the /mode command
fn complete_mode_flags(&self, line: &str) -> Result<(usize, Vec<Pair>)> {
let modes = ["auto", "approve", "chat"];
let parts: Vec<&str> = line.split_whitespace().collect();
// If we're just after "/mode" with a space, show all options
if line == "/mode " {
return Ok((
line.len(),
modes
.iter()
.map(|mode| Pair {
display: mode.to_string(),
replacement: format!("{} ", mode),
})
.collect(),
));
}
// If we're typing a mode name, show the flags for that mode
if parts.len() == 2 {
let partial = parts[1].to_lowercase();
return Ok((
line.len() - partial.len(),
modes
.iter()
.filter(|mode| mode.to_lowercase().starts_with(&partial.to_lowercase()))
.map(|mode| Pair {
display: mode.to_string(),
replacement: format!("{} ", mode),
})
.collect(),
));
}
// No completions available
Ok((line.len(), vec![]))
}
/// Complete slash commands
fn complete_slash_commands(&self, line: &str) -> Result<(usize, Vec<Pair>)> {
// Define available slash commands
@@ -88,6 +128,7 @@ impl GooseCompleter {
"/builtin",
"/prompts",
"/prompt",
"/mode",
];
// Find commands that match the prefix
@@ -292,6 +333,10 @@ impl Completer for GooseCompleter {
));
}
}
if line.starts_with("/mode") {
return self.complete_mode_flags(line);
}
}
// Default: no completions
+24 -9
View File
@@ -1,10 +1,9 @@
use super::completion::GooseCompleter;
use anyhow::Result;
use rustyline::Editor;
use shlex;
use std::collections::HashMap;
use super::completion::GooseCompleter;
#[derive(Debug)]
pub enum InputResult {
Message(String),
@@ -15,6 +14,7 @@ pub enum InputResult {
Retry,
ListPrompts(Option<String>),
PromptCommand(PromptCommandOptions),
GooseMode(String),
}
#[derive(Debug)]
@@ -65,6 +65,14 @@ pub fn get_input(
fn handle_slash_command(input: &str) -> Option<InputResult> {
let input = input.trim();
// Command prefix constants
const CMD_PROMPTS: &str = "/prompts ";
const CMD_PROMPT: &str = "/prompt";
const CMD_PROMPT_WITH_SPACE: &str = "/prompt ";
const CMD_EXTENSION: &str = "/extension ";
const CMD_BUILTIN: &str = "/builtin ";
const CMD_MODE: &str = "/mode ";
match input {
"/exit" | "/quit" => Some(InputResult::Exit),
"/?" | "/help" => {
@@ -73,20 +81,20 @@ fn handle_slash_command(input: &str) -> Option<InputResult> {
}
"/t" => Some(InputResult::ToggleTheme),
"/prompts" => Some(InputResult::ListPrompts(None)),
s if s.starts_with("/prompts ") => {
s if s.starts_with(CMD_PROMPTS) => {
// Parse arguments for /prompts command
let args = s.strip_prefix("/prompts ").unwrap_or_default();
let args = s.strip_prefix(CMD_PROMPTS).unwrap_or_default();
parse_prompts_command(args)
}
s if s.starts_with("/prompt") => {
if s == "/prompt" {
s if s.starts_with(CMD_PROMPT) => {
if s == CMD_PROMPT {
// No arguments case
Some(InputResult::PromptCommand(PromptCommandOptions {
name: String::new(), // Empty name will trigger the error message in the rendering
info: false,
arguments: HashMap::new(),
}))
} else if let Some(stripped) = s.strip_prefix("/prompt ") {
} else if let Some(stripped) = s.strip_prefix(CMD_PROMPT_WITH_SPACE) {
// Has arguments case
parse_prompt_command(stripped)
} else {
@@ -94,8 +102,15 @@ fn handle_slash_command(input: &str) -> Option<InputResult> {
None
}
}
s if s.starts_with("/extension ") => Some(InputResult::AddExtension(s[11..].to_string())),
s if s.starts_with("/builtin ") => Some(InputResult::AddBuiltin(s[9..].to_string())),
s if s.starts_with(CMD_EXTENSION) => Some(InputResult::AddExtension(
s[CMD_EXTENSION.len()..].to_string(),
)),
s if s.starts_with(CMD_BUILTIN) => {
Some(InputResult::AddBuiltin(s[CMD_BUILTIN.len()..].to_string()))
}
s if s.starts_with(CMD_MODE) => {
Some(InputResult::GooseMode(s[CMD_MODE.len()..].to_string()))
}
_ => None,
}
}
+22
View File
@@ -14,6 +14,7 @@ use etcetera::choose_app_strategy;
use etcetera::AppStrategy;
use goose::agents::extension::{Envs, ExtensionConfig};
use goose::agents::Agent;
use goose::config::Config;
use goose::message::{Message, MessageContent};
use goose::session;
use mcp_core::handler::ToolError;
@@ -325,6 +326,27 @@ impl Session {
Err(e) => output::render_error(&e.to_string()),
}
}
input::InputResult::GooseMode(mode) => {
save_history(&mut editor);
let config = Config::global();
let mode = mode.to_lowercase();
// Check if mode is valid
if !["auto", "approve", "chat"].contains(&mode.as_str()) {
output::render_error(&format!(
"Invalid mode '{}'. Mode must be one of: auto, approve, chat",
mode
));
continue;
}
config
.set("GOOSE_MODE", Value::String(mode.to_string()))
.unwrap();
println!("Goose mode set to '{}'", mode);
continue;
}
input::InputResult::PromptCommand(opts) => {
save_history(&mut editor);