9d7de43eec
Signed-off-by: Bradley Axen <baxen@squareup.com>
198 lines
5.6 KiB
Rust
198 lines
5.6 KiB
Rust
use goose::conversation::message::Message;
|
|
use goose::session::session_manager::SessionStorage;
|
|
use goose::session::thread_manager::ThreadManager;
|
|
use rmcp::model::CallToolRequestParams;
|
|
use std::sync::Arc;
|
|
use tempfile::TempDir;
|
|
|
|
async fn setup() -> (ThreadManager, TempDir) {
|
|
let tmp = TempDir::new().unwrap();
|
|
let storage = SessionStorage::create(tmp.path()).await.unwrap();
|
|
let tm = ThreadManager::new(Arc::new(storage));
|
|
(tm, tmp)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn consecutive_text_chunks_are_coalesced() {
|
|
let (tm, _tmp) = setup().await;
|
|
let thread = tm.create_thread(None, None, None).await.unwrap();
|
|
|
|
// Simulate streaming: three consecutive assistant text chunks.
|
|
tm.append_message(
|
|
&thread.id,
|
|
Some("s1"),
|
|
&Message::assistant().with_text("Hello"),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
tm.append_message(
|
|
&thread.id,
|
|
Some("s1"),
|
|
&Message::assistant().with_text(" world"),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
tm.append_message(&thread.id, Some("s1"), &Message::assistant().with_text("!"))
|
|
.await
|
|
.unwrap();
|
|
|
|
let messages = tm.list_messages(&thread.id).await.unwrap();
|
|
assert_eq!(messages.len(), 1, "should coalesce into a single row");
|
|
assert_eq!(messages[0].as_concat_text(), "Hello world!");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn role_change_prevents_coalescing() {
|
|
let (tm, _tmp) = setup().await;
|
|
let thread = tm.create_thread(None, None, None).await.unwrap();
|
|
|
|
tm.append_message(&thread.id, Some("s1"), &Message::user().with_text("Hi"))
|
|
.await
|
|
.unwrap();
|
|
tm.append_message(
|
|
&thread.id,
|
|
Some("s1"),
|
|
&Message::assistant().with_text("Hey"),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let messages = tm.list_messages(&thread.id).await.unwrap();
|
|
assert_eq!(messages.len(), 2, "different roles should not coalesce");
|
|
assert_eq!(messages[0].as_concat_text(), "Hi");
|
|
assert_eq!(messages[1].as_concat_text(), "Hey");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn non_text_content_breaks_coalescing() {
|
|
let (tm, _tmp) = setup().await;
|
|
let thread = tm.create_thread(None, None, None).await.unwrap();
|
|
|
|
// Text, then tool request, then more text — should be 3 rows.
|
|
tm.append_message(
|
|
&thread.id,
|
|
Some("s1"),
|
|
&Message::assistant().with_text("Let me check"),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let tool_msg = Message::assistant().with_tool_request(
|
|
"call_1",
|
|
Ok(CallToolRequestParams::new("shell").with_arguments(
|
|
serde_json::json!({"command": "ls"})
|
|
.as_object()
|
|
.unwrap()
|
|
.clone(),
|
|
)),
|
|
);
|
|
tm.append_message(&thread.id, Some("s1"), &tool_msg)
|
|
.await
|
|
.unwrap();
|
|
|
|
tm.append_message(
|
|
&thread.id,
|
|
Some("s1"),
|
|
&Message::assistant().with_text("Done"),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let messages = tm.list_messages(&thread.id).await.unwrap();
|
|
assert_eq!(messages.len(), 3, "tool request should break coalescing");
|
|
assert_eq!(messages[0].as_concat_text(), "Let me check");
|
|
assert_eq!(messages[2].as_concat_text(), "Done");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn text_after_tool_response_not_coalesced_with_tool() {
|
|
let (tm, _tmp) = setup().await;
|
|
let thread = tm.create_thread(None, None, None).await.unwrap();
|
|
|
|
// A tool request message (non-text) followed by text — should not coalesce.
|
|
let tool_msg = Message::assistant().with_tool_request(
|
|
"call_1",
|
|
Ok(CallToolRequestParams::new("shell").with_arguments(
|
|
serde_json::json!({"command": "ls"})
|
|
.as_object()
|
|
.unwrap()
|
|
.clone(),
|
|
)),
|
|
);
|
|
tm.append_message(&thread.id, Some("s1"), &tool_msg)
|
|
.await
|
|
.unwrap();
|
|
|
|
tm.append_message(
|
|
&thread.id,
|
|
Some("s1"),
|
|
&Message::assistant().with_text("Result"),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let messages = tm.list_messages(&thread.id).await.unwrap();
|
|
assert_eq!(
|
|
messages.len(),
|
|
2,
|
|
"text should not coalesce with non-text predecessor"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn empty_message_not_coalesced() {
|
|
let (tm, _tmp) = setup().await;
|
|
let thread = tm.create_thread(None, None, None).await.unwrap();
|
|
|
|
tm.append_message(
|
|
&thread.id,
|
|
Some("s1"),
|
|
&Message::assistant().with_text("Hello"),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
// An empty assistant message (no content items).
|
|
let empty = Message::assistant();
|
|
tm.append_message(&thread.id, Some("s1"), &empty)
|
|
.await
|
|
.unwrap();
|
|
|
|
let messages = tm.list_messages(&thread.id).await.unwrap();
|
|
// Empty message should be inserted as a new row (not coalesced).
|
|
assert_eq!(messages.len(), 2);
|
|
assert_eq!(messages[0].as_concat_text(), "Hello");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn metadata_change_prevents_coalescing() {
|
|
let (tm, _tmp) = setup().await;
|
|
let thread = tm.create_thread(None, None, None).await.unwrap();
|
|
|
|
tm.append_message(
|
|
&thread.id,
|
|
Some("s1"),
|
|
&Message::assistant().with_text("Visible"),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
tm.append_message(
|
|
&thread.id,
|
|
Some("s1"),
|
|
&Message::assistant().with_text(" hidden").agent_only(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let messages = tm.list_messages(&thread.id).await.unwrap();
|
|
assert_eq!(
|
|
messages.len(),
|
|
2,
|
|
"metadata boundary should break coalescing"
|
|
);
|
|
assert!(messages[0].metadata.user_visible);
|
|
assert!(!messages[1].metadata.user_visible);
|
|
assert_eq!(messages[0].as_concat_text(), "Visible");
|
|
assert_eq!(messages[1].as_concat_text(), " hidden");
|
|
}
|