Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion crates/goose/src/prompts/session_name.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Generate a short title (four words or less) that describes the topic of the user's messages. Reply with only the title, nothing else.
Generate a short title (four words or less) that describes the topic of the user's messages.
Reply with only the title, nothing else. Do not show your reasoning.

Examples:
- "how do I reverse a list in python?" → Python list reversal
Expand Down
108 changes: 107 additions & 1 deletion crates/goose/src/providers/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,58 @@ fn strip_xml_tags(text: &str) -> String {
TAG_RE.replace_all(&pass1, "").into_owned()
}

fn extract_short_title(text: &str) -> String {
let word_count = text.split_whitespace().count();
if word_count <= 8 {
return text.to_string();
}

{
let mut results = Vec::new();
let mut quote_char: Option<char> = None;
let mut current = String::new();
let mut prev_char: Option<char> = None;

for ch in text.chars() {
match quote_char {
None => {
if matches!(ch, '"' | '\'' | '`') {
let after_alnum = prev_char.map(|p| p.is_alphanumeric()).unwrap_or(false);
if !after_alnum {
quote_char = Some(ch);
current.clear();
}
}
}
Some(q) => {
if ch == q {
let trimmed = current.trim().to_string();
let wc = trimmed.split_whitespace().count();
if (2..=8).contains(&wc) {
results.push(trimmed);
}
quote_char = None;
current.clear();
} else {
current.push(ch);
}
}
}
prev_char = Some(ch);
}

if let Some(title) = results.last() {
return title.clone();
}
}

if let Some(last) = text.lines().rev().find(|l| !l.trim().is_empty()) {
return last.trim().to_string();
}

text.to_string()
}

/// A global store for the current model being used, we use this as when a provider returns, it tells us the real model, not an alias
pub static CURRENT_MODEL: Lazy<Mutex<Option<String>>> = Lazy::new(|| Mutex::new(None));

Expand Down Expand Up @@ -632,7 +684,7 @@ pub trait Provider: Send + Sync {
.collect::<Vec<_>>()
.join(" ");

Ok(safe_truncate(&description, 100))
Ok(safe_truncate(&extract_short_title(&description), 100))
}

/// Configure OAuth authentication for this provider
Expand Down Expand Up @@ -752,6 +804,60 @@ mod tests {
);
}

#[test]
fn test_extract_short_title() {
assert_eq!(extract_short_title("List files"), "List files");
assert_eq!(
extract_short_title(
r#"blah blah blah blah blah blah blah blah blah "List files in folder""#
),
"List files in folder"
);
assert_eq!(
extract_short_title(
"blah blah blah blah blah blah blah blah blah `View current files`"
),
"View current files"
);
assert_eq!(
extract_short_title(
r#"stuff stuff stuff stuff stuff stuff stuff stuff "Abc title" "Zzz title""#
),
"Zzz title"
);
assert_eq!(
extract_short_title(
"long long long long long long long long long\nList files in folder"
),
"List files in folder"
);
assert_eq!(
extract_short_title(
r#"lots of words here and there and more and more "single" final line here"#
),
"lots of words here and there and more and more \"single\" final line here"
);
assert_eq!(extract_short_title("Hello world"), "Hello world");
assert_eq!(
extract_short_title(
r#"1. Analyze the request. 2. The user's message says list files. 3. "List current folder files" fits perfectly. Result: List current folder files"#
),
"List current folder files"
);
assert_eq!(
extract_short_title(
r#"the user's phrasing is about listing files and the user's intent is clear. "List folder files" is best"#
),
"List folder files"
);
assert_eq!(
extract_short_title(
"lots of reasoning here about what to call it\nList current folder files"
),
"List current folder files"
);
}

#[test]
fn test_usage_creation() {
let usage = Usage::new(Some(10), Some(20), Some(30));
Expand Down
Loading