From db44a40a9eb21ba1370fe36fed8e10e630d00f67 Mon Sep 17 00:00:00 2001 From: Douwe Osinga Date: Sun, 22 Feb 2026 11:05:29 -0500 Subject: [PATCH] Small model naming (#7394) Co-authored-by: Douwe Osinga --- crates/goose/src/prompts/session_name.md | 3 +- crates/goose/src/providers/base.rs | 108 ++++++++++++++++++++++- 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/crates/goose/src/prompts/session_name.md b/crates/goose/src/prompts/session_name.md index 1a40184a4d..0d53d40d4a 100644 --- a/crates/goose/src/prompts/session_name.md +++ b/crates/goose/src/prompts/session_name.md @@ -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 diff --git a/crates/goose/src/providers/base.rs b/crates/goose/src/providers/base.rs index 4a0c4c1bbc..89382f6cfd 100644 --- a/crates/goose/src/providers/base.rs +++ b/crates/goose/src/providers/base.rs @@ -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 = None; + let mut current = String::new(); + let mut prev_char: Option = 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>> = Lazy::new(|| Mutex::new(None)); @@ -632,7 +684,7 @@ pub trait Provider: Send + Sync { .collect::>() .join(" "); - Ok(safe_truncate(&description, 100)) + Ok(safe_truncate(&extract_short_title(&description), 100)) } /// Configure OAuth authentication for this provider @@ -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));