From 176f983dc9b3e4fb8df69a296400a2376d62b206 Mon Sep 17 00:00:00 2001 From: lucaronin Date: Thu, 30 Apr 2026 03:24:11 +0200 Subject: [PATCH] fix: render opencode text parts after tools --- src-tauri/src/opencode_events.rs | 11 +++++- src-tauri/src/opencode_events_tests.rs | 47 ++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src-tauri/src/opencode_events.rs b/src-tauri/src/opencode_events.rs index 7cdc04b7..db467b6c 100644 --- a/src-tauri/src/opencode_events.rs +++ b/src-tauri/src/opencode_events.rs @@ -19,7 +19,7 @@ where emit_session_event(json, emit); } - match json["type"].as_str().unwrap_or_default() { + match event_type(json) { "message" | "text" => emit_text(json, emit), "reasoning" => emit_reasoning(json, emit), "tool_use" | "tool" => emit_tool_start(json, emit), @@ -29,6 +29,15 @@ where } } +fn event_type(json: &serde_json::Value) -> &str { + let direct = json["type"].as_str().unwrap_or_default(); + match direct { + "session" | "message" | "text" | "reasoning" | "tool_use" | "tool" | "tool_result" + | "tool_done" | "error" => direct, + _ => json["part"]["type"].as_str().unwrap_or(direct), + } +} + pub(crate) fn session_id(json: &serde_json::Value) -> Option<&str> { json["sessionID"] .as_str() diff --git a/src-tauri/src/opencode_events_tests.rs b/src-tauri/src/opencode_events_tests.rs index c2633028..54002582 100644 --- a/src-tauri/src/opencode_events_tests.rs +++ b/src-tauri/src/opencode_events_tests.rs @@ -107,6 +107,53 @@ fn dispatch_maps_part_backed_reasoning_and_text() { )); } +#[test] +fn dispatch_maps_final_text_after_tool_part_wrappers() { + let events = dispatch_events([ + serde_json::json!({ + "type": "part", + "part": { + "id": "prt_tool_1", + "type": "tool", + "tool": "webfetch", + "input": { "url": "https://example.com" } + } + }), + serde_json::json!({ + "type": "part", + "part": { + "id": "prt_text_1", + "type": "text", + "text": "Final answer after tool output." + } + }), + serde_json::json!({ + "type": "part", + "part": { + "id": "prt_finish_1", + "type": "step-finish", + "reason": "stop" + } + }), + ]); + + assert!(matches!( + &events[0], + AiAgentStreamEvent::ToolStart { + tool_name, + tool_id, + input, + } if tool_name == "webfetch" + && tool_id == "prt_tool_1" + && input.as_deref() == Some(r#"{"url":"https://example.com"}"#) + )); + assert!(matches!( + &events[1], + AiAgentStreamEvent::TextDelta { text } if text == "Final answer after tool output." + )); + assert_eq!(events.len(), 2); +} + #[test] fn dispatch_maps_tool_events() { let direct = dispatch_events([