fix: preserve replay message timestamps (#8942)

Signed-off-by: Matt Toohey <contact@matttoohey.com>
This commit is contained in:
Matt Toohey
2026-05-02 03:16:01 +10:00
committed by GitHub
parent 5da6ad6f8c
commit 501c1edab5
7 changed files with 466 additions and 19 deletions
+109 -6
View File
@@ -1670,6 +1670,42 @@ fn extract_tool_call_update_meta(
Some(meta_map)
}
fn replay_message_meta(message: &Message) -> Meta {
let mut meta = serde_json::Map::new();
meta.insert(
"goose".to_string(),
serde_json::Value::Object(replay_message_goose_meta(message)),
);
meta
}
fn replay_message_goose_meta(message: &Message) -> serde_json::Map<String, serde_json::Value> {
let mut goose = serde_json::Map::new();
goose.insert("created".to_string(), serde_json::json!(message.created));
if let Some(id) = &message.id {
goose.insert("messageId".to_string(), serde_json::json!(id));
}
goose
}
fn merge_replay_message_meta(meta: Option<Meta>, message: &Message) -> Meta {
let replay_goose = replay_message_goose_meta(message);
let mut meta = meta.unwrap_or_default();
let goose_value = meta
.entry("goose".to_string())
.or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
if let serde_json::Value::Object(goose) = goose_value {
for (key, value) in replay_goose {
goose.insert(key, value);
}
} else {
*goose_value = serde_json::Value::Object(replay_goose);
}
meta
}
fn build_tool_call_content(tool_result: &ToolResult<CallToolResult>) -> Vec<ToolCallContent> {
match tool_result {
Ok(result) => result
@@ -2119,7 +2155,8 @@ impl GooseAcpAgent {
),
);
}
let chunk = ContentChunk::new(ContentBlock::Text(tc));
let chunk = ContentChunk::new(ContentBlock::Text(tc))
.meta(replay_message_meta(message));
let update = match message.role {
Role::User => SessionUpdate::UserMessageChunk(chunk),
Role::Assistant => SessionUpdate::AgentMessageChunk(chunk),
@@ -2147,7 +2184,8 @@ impl GooseAcpAgent {
ToolCallId::new(tool_request.id.clone()),
format_tool_name(&tool_name),
)
.status(ToolCallStatus::Pending),
.status(ToolCallStatus::Pending)
.meta(replay_message_meta(message)),
),
))?;
}
@@ -2187,7 +2225,10 @@ impl GooseAcpAgent {
let update =
ToolCallUpdate::new(ToolCallId::new(tool_response.id.clone()), fields)
.meta(extract_tool_call_update_meta(tool_response));
.meta(merge_replay_message_meta(
extract_tool_call_update_meta(tool_response),
message,
));
cx.send_notification(SessionNotification::new(
args.session_id.clone(),
SessionUpdate::ToolCallUpdate(update),
@@ -2196,9 +2237,12 @@ impl GooseAcpAgent {
MessageContent::Thinking(thinking) => {
cx.send_notification(SessionNotification::new(
args.session_id.clone(),
SessionUpdate::AgentThoughtChunk(ContentChunk::new(
ContentBlock::Text(TextContent::new(thinking.thinking.clone())),
)),
SessionUpdate::AgentThoughtChunk(
ContentChunk::new(ContentBlock::Text(TextContent::new(
thinking.thinking.clone(),
)))
.meta(replay_message_meta(message)),
),
))?;
}
_ => {}
@@ -3252,6 +3296,65 @@ print(\"hello, world\")
);
}
#[test]
fn test_merge_replay_message_meta_preserves_existing_goose_meta() {
let message = Message::new(Role::Assistant, 1_700_000_000, vec![]).with_id("msg_1");
let existing = serde_json::from_value(serde_json::json!({
"goose": {
"mcpApp": {
"resourceUri": "ui://trusted/app",
"extensionName": "weather",
"toolName": "weather__render",
},
},
}))
.unwrap();
let merged = merge_replay_message_meta(Some(existing), &message);
assert_eq!(
merged.get("goose"),
Some(&serde_json::json!({
"created": 1_700_000_000,
"messageId": "msg_1",
"mcpApp": {
"resourceUri": "ui://trusted/app",
"extensionName": "weather",
"toolName": "weather__render",
},
})),
);
}
#[test]
fn test_merge_replay_message_meta_creates_fresh_when_none() {
let message = Message::new(Role::Assistant, 1_700_000_000, vec![]).with_id("msg_2");
let merged = merge_replay_message_meta(None, &message);
assert_eq!(
merged.get("goose"),
Some(&serde_json::json!({
"created": 1_700_000_000,
"messageId": "msg_2",
})),
);
}
#[test]
fn test_merge_replay_message_meta_omits_message_id_when_none() {
let message = Message::new(Role::Assistant, 1_700_000_000, vec![]);
let merged = merge_replay_message_meta(None, &message);
assert_eq!(
merged.get("goose"),
Some(&serde_json::json!({
"created": 1_700_000_000,
})),
);
}
fn make_session_with_usage(
total_tokens: Option<i32>,
input_tokens: Option<i32>,