fix(acp): coalesce streaming chunks under one message id (#8788)
Signed-off-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
@@ -356,6 +356,13 @@ impl AcpProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fresh_text_run() -> (String, i64) {
|
||||||
|
(
|
||||||
|
uuid::Uuid::new_v4().to_string(),
|
||||||
|
chrono::Utc::now().timestamp(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Provider for AcpProvider {
|
impl Provider for AcpProvider {
|
||||||
fn get_name(&self) -> &str {
|
fn get_name(&self) -> &str {
|
||||||
@@ -451,22 +458,36 @@ impl Provider for AcpProvider {
|
|||||||
Ok(Box::pin(try_stream! {
|
Ok(Box::pin(try_stream! {
|
||||||
let mut suppress_text = false;
|
let mut suppress_text = false;
|
||||||
let mut rejected_tool_calls: HashSet<String> = HashSet::new();
|
let mut rejected_tool_calls: HashSet<String> = HashSet::new();
|
||||||
|
// Stable id+timestamp per contiguous run so Desktop coalesces chunks into one bubble.
|
||||||
|
let mut text_run: Option<(String, i64)> = None;
|
||||||
|
let mut thought_run: Option<(String, i64)> = None;
|
||||||
|
|
||||||
while let Some(update) = rx.recv().await {
|
while let Some(update) = rx.recv().await {
|
||||||
match update {
|
match update {
|
||||||
AcpUpdate::Text(text) => {
|
AcpUpdate::Text(text) => {
|
||||||
if !suppress_text {
|
if !suppress_text {
|
||||||
let message = Message::assistant().with_text(text);
|
let (id, ts) = text_run
|
||||||
|
.get_or_insert_with(fresh_text_run)
|
||||||
|
.clone();
|
||||||
|
let message = Message::new(Role::Assistant, ts, vec![])
|
||||||
|
.with_text(text)
|
||||||
|
.with_id(id);
|
||||||
yield (Some(message), None);
|
yield (Some(message), None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AcpUpdate::Thought(text) => {
|
AcpUpdate::Thought(text) => {
|
||||||
let message = Message::assistant()
|
let (id, ts) = thought_run
|
||||||
|
.get_or_insert_with(fresh_text_run)
|
||||||
|
.clone();
|
||||||
|
let message = Message::new(Role::Assistant, ts, vec![])
|
||||||
.with_thinking(text, "")
|
.with_thinking(text, "")
|
||||||
.with_visibility(true, false);
|
.with_visibility(true, false)
|
||||||
|
.with_id(id);
|
||||||
yield (Some(message), None);
|
yield (Some(message), None);
|
||||||
}
|
}
|
||||||
AcpUpdate::ToolCallStart { id, name, kind, raw_input } => {
|
AcpUpdate::ToolCallStart { id, name, kind, raw_input } => {
|
||||||
|
text_run = None;
|
||||||
|
thought_run = None;
|
||||||
if reject_all_tools {
|
if reject_all_tools {
|
||||||
suppress_text = true;
|
suppress_text = true;
|
||||||
rejected_tool_calls.insert(id);
|
rejected_tool_calls.insert(id);
|
||||||
@@ -498,6 +519,8 @@ impl Provider for AcpProvider {
|
|||||||
content,
|
content,
|
||||||
is_error,
|
is_error,
|
||||||
} => {
|
} => {
|
||||||
|
text_run = None;
|
||||||
|
thought_run = None;
|
||||||
if rejected_tool_calls.remove(&id) {
|
if rejected_tool_calls.remove(&id) {
|
||||||
// In chat mode no tool_request was emitted (suppressed at
|
// In chat mode no tool_request was emitted (suppressed at
|
||||||
// ToolCallStart), so surface a plain text message. In other
|
// ToolCallStart), so surface a plain text message. In other
|
||||||
@@ -527,6 +550,8 @@ impl Provider for AcpProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
AcpUpdate::PermissionRequest { request, response_tx } => {
|
AcpUpdate::PermissionRequest { request, response_tx } => {
|
||||||
|
text_run = None;
|
||||||
|
thought_run = None;
|
||||||
if let Some(decision) = permission_decision_from_mode(goose_mode) {
|
if let Some(decision) = permission_decision_from_mode(goose_mode) {
|
||||||
if decision.should_record_rejection() {
|
if decision.should_record_rejection() {
|
||||||
rejected_tool_calls.insert(request.tool_call.tool_call_id.0.to_string());
|
rejected_tool_calls.insert(request.tool_call.tool_call_id.0.to_string());
|
||||||
|
|||||||
Reference in New Issue
Block a user