commit 5cd46712995d85ceedc7e7ed149f4aa4faba8f11
parent b1992cf43f52e2b2eedafaec174864dba9fb99be
Author: William Casarin <jb55@jb55.com>
Date: Thu, 19 Feb 2026 11:17:39 -0800
dave: merge ToolResult into ToolResponse as ExecutedTool variant
Remove the separate Message::ToolResult variant and unify tool
responses under Message::ToolResponse. ToolResult is renamed to
ExecutedTool and added as a ToolResponses variant, representing
tools already executed by an agentic backend. This eliminates the
duplication between ToolResult and ToolResponse in the Message enum.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
7 files changed, 77 insertions(+), 51 deletions(-)
diff --git a/crates/notedeck_dave/src/backend/claude.rs b/crates/notedeck_dave/src/backend/claude.rs
@@ -5,8 +5,8 @@ use crate::backend::tool_summary::{
};
use crate::backend::traits::AiBackend;
use crate::messages::{
- CompactionInfo, DaveApiResponse, ParsedMarkdown, PendingPermission, PermissionRequest,
- PermissionResponse, SubagentInfo, SubagentStatus, ToolResult,
+ CompactionInfo, DaveApiResponse, ExecutedTool, ParsedMarkdown, PendingPermission,
+ PermissionRequest, PermissionResponse, SubagentInfo, SubagentStatus,
};
use crate::tools::Tool;
use crate::Message;
@@ -105,10 +105,9 @@ impl ClaudeBackend {
| Message::ToolResponse(_)
| Message::Error(_)
| Message::PermissionRequest(_)
- | Message::ToolResult(_)
| Message::CompactionComplete(_)
| Message::Subagent(_) => {
- // Skip tool-related, error, permission, tool result, compaction, and subagent messages
+ // Skip tool-related, error, permission, compaction, and subagent messages
}
}
}
@@ -523,7 +522,7 @@ async fn session_actor(
// Attach parent subagent context (top of stack)
let parent_task_id = subagent_stack.last().cloned();
let summary = format_tool_summary(&tool_name, &tool_input, &result_value);
- let tool_result = ToolResult { tool_name, summary, parent_task_id };
+ let tool_result = ExecutedTool { tool_name, summary, parent_task_id };
let _ = response_tx.send(DaveApiResponse::ToolResult(tool_result));
ctx.request_repaint();
}
diff --git a/crates/notedeck_dave/src/lib.rs b/crates/notedeck_dave/src/lib.rs
@@ -40,8 +40,9 @@ use std::time::Instant;
pub use avatar::DaveAvatar;
pub use config::{AiMode, AiProvider, DaveSettings, ModelConfig};
pub use messages::{
- AskUserQuestionInput, AssistantMessage, DaveApiResponse, Message, PermissionResponse,
- PermissionResponseType, QuestionAnswer, SessionInfo, SubagentInfo, SubagentStatus, ToolResult,
+ AskUserQuestionInput, AssistantMessage, DaveApiResponse, ExecutedTool, Message,
+ PermissionResponse, PermissionResponseType, QuestionAnswer, SessionInfo, SubagentInfo,
+ SubagentStatus,
};
pub use quaternion::Quaternion;
pub use session::{ChatSession, SessionId, SessionManager};
@@ -675,7 +676,9 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
}
}
if let Some(result) = session.fold_tool_result(result) {
- session.chat.push(Message::ToolResult(result));
+ session
+ .chat
+ .push(Message::ToolResponse(ToolResponse::executed_tool(result)));
}
}
@@ -1747,11 +1750,13 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
.to_string();
session
.chat
- .push(Message::ToolResult(crate::messages::ToolResult {
- tool_name,
- summary,
- parent_task_id: None,
- }));
+ .push(Message::ToolResponse(ToolResponse::executed_tool(
+ crate::messages::ExecutedTool {
+ tool_name,
+ summary,
+ parent_task_id: None,
+ },
+ )));
}
Some("permission_request") => {
if let Ok(content_json) = serde_json::from_str::<serde_json::Value>(content)
diff --git a/crates/notedeck_dave/src/messages.rs b/crates/notedeck_dave/src/messages.rs
@@ -1,4 +1,4 @@
-use crate::tools::{ToolCall, ToolResponse};
+use crate::tools::{ToolCall, ToolResponse, ToolResponses};
use async_openai::types::*;
use md_stream::{MdElement, Partial, StreamParser};
@@ -102,9 +102,10 @@ pub enum PermissionResponseType {
Denied,
}
-/// Metadata about a completed tool execution
-#[derive(Debug, Clone)]
-pub struct ToolResult {
+/// Metadata about a completed tool execution from an agentic backend.
+/// Used as a variant in `ToolResponses` to unify with other tool responses.
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct ExecutedTool {
pub tool_name: String,
pub summary: String, // e.g., "154 lines", "exit 0", "3 matches"
/// Which subagent (Task tool_use_id) produced this result, if any
@@ -159,7 +160,7 @@ pub struct SubagentInfo {
/// Maximum output size to keep (for size-restricted window)
pub max_output_size: usize,
/// Tool results produced by this subagent
- pub tool_results: Vec<ToolResult>,
+ pub tool_results: Vec<ExecutedTool>,
}
/// An assistant message with incremental markdown parsing support.
@@ -303,8 +304,6 @@ pub enum Message {
ToolResponse(ToolResponse),
/// A permission request from the AI that needs user response
PermissionRequest(PermissionRequest),
- /// Result metadata from a completed tool execution
- ToolResult(ToolResult),
/// Conversation was compacted
CompactionComplete(CompactionInfo),
/// A subagent spawned by Task tool
@@ -327,7 +326,7 @@ pub enum DaveApiResponse {
/// A permission request that needs to be displayed to the user
PermissionRequest(PendingPermission),
/// Metadata from a completed tool execution
- ToolResult(ToolResult),
+ ToolResult(ExecutedTool),
/// Session initialization info from Claude Code CLI
SessionInfo(SessionInfo),
/// Subagent spawned by Task tool
@@ -388,6 +387,11 @@ impl Message {
)),
Message::ToolResponse(resp) => {
+ // ExecutedTool results are UI-only, not sent to the API
+ if matches!(resp.responses(), ToolResponses::ExecutedTool(_)) {
+ return None;
+ }
+
let tool_response = resp.responses().format_for_dave(txn, ndb);
Some(ChatCompletionRequestMessage::Tool(
@@ -401,9 +405,6 @@ impl Message {
// Permission requests are UI-only, not sent to the API
Message::PermissionRequest(_) => None,
- // Tool results are UI-only, not sent to the API
- Message::ToolResult(_) => None,
-
// Compaction complete is UI-only, not sent to the API
Message::CompactionComplete(_) => None,
diff --git a/crates/notedeck_dave/src/session.rs b/crates/notedeck_dave/src/session.rs
@@ -6,8 +6,8 @@ use crate::agent_status::AgentStatus;
use crate::config::AiMode;
use crate::git_status::GitStatusCache;
use crate::messages::{
- AnswerSummary, CompactionInfo, PermissionResponse, PermissionResponseType, QuestionAnswer,
- SessionInfo, SubagentStatus, ToolResult,
+ AnswerSummary, CompactionInfo, ExecutedTool, PermissionResponse, PermissionResponseType,
+ QuestionAnswer, SessionInfo, SubagentStatus,
};
use crate::session_events::ThreadingState;
use crate::{DaveApiResponse, Message};
@@ -255,7 +255,11 @@ impl AgenticSessionData {
/// Try to fold a tool result into its parent subagent.
/// Returns None if folded, Some(result) if it couldn't be folded.
- pub fn fold_tool_result(&self, chat: &mut [Message], result: ToolResult) -> Option<ToolResult> {
+ pub fn fold_tool_result(
+ &self,
+ chat: &mut [Message],
+ result: ExecutedTool,
+ ) -> Option<ExecutedTool> {
let parent_id = result.parent_task_id.as_ref()?;
let &idx = self.subagent_indices.get(parent_id)?;
if let Some(Message::Subagent(subagent)) = chat.get_mut(idx) {
@@ -421,7 +425,7 @@ impl ChatSession {
/// Try to fold a tool result into its parent subagent.
/// Returns None if folded, Some(result) if it couldn't be folded.
- pub fn fold_tool_result(&mut self, result: ToolResult) -> Option<ToolResult> {
+ pub fn fold_tool_result(&mut self, result: ExecutedTool) -> Option<ExecutedTool> {
if let Some(ref agentic) = self.agentic {
agentic.fold_tool_result(&mut self.chat, result)
} else {
diff --git a/crates/notedeck_dave/src/session_loader.rs b/crates/notedeck_dave/src/session_loader.rs
@@ -4,9 +4,10 @@
//! orders them by created_at, and converts them into `Message` variants
//! for populating the chat UI.
-use crate::messages::{AssistantMessage, PermissionRequest, PermissionResponseType, ToolResult};
+use crate::messages::{AssistantMessage, ExecutedTool, PermissionRequest, PermissionResponseType};
use crate::session::PermissionTracker;
use crate::session_events::{get_tag_value, is_conversation_role, AI_CONVERSATION_KIND};
+use crate::tools::ToolResponse;
use crate::Message;
use nostrdb::{Filter, Ndb, NoteKey, Transaction};
use std::collections::HashSet;
@@ -164,13 +165,15 @@ pub fn load_session_messages(ndb: &Ndb, txn: &Transaction, session_id: &str) ->
)),
Some("tool_result") => {
let summary = truncate(content, 200);
- Some(Message::ToolResult(ToolResult {
- tool_name: get_tag_value(note, "tool-name")
- .unwrap_or("tool")
- .to_string(),
- summary,
- parent_task_id: None,
- }))
+ Some(Message::ToolResponse(ToolResponse::executed_tool(
+ ExecutedTool {
+ tool_name: get_tag_value(note, "tool-name")
+ .unwrap_or("tool")
+ .to_string(),
+ summary,
+ parent_task_id: None,
+ },
+ )))
}
Some("permission_request") => {
if let Ok(content_json) = serde_json::from_str::<serde_json::Value>(content) {
diff --git a/crates/notedeck_dave/src/tools.rs b/crates/notedeck_dave/src/tools.rs
@@ -1,3 +1,4 @@
+use crate::messages::ExecutedTool;
use async_openai::types::*;
use chrono::DateTime;
use enostr::{NoteId, Pubkey};
@@ -98,6 +99,7 @@ pub enum ToolResponses {
Error(String),
Query(QueryResponse),
PresentNotes(i32),
+ ExecutedTool(ExecutedTool),
}
#[derive(Debug, Clone)]
@@ -344,6 +346,13 @@ impl ToolResponse {
}
}
+ pub fn executed_tool(result: ExecutedTool) -> Self {
+ Self {
+ id: String::new(),
+ typ: ToolResponses::ExecutedTool(result),
+ }
+ }
+
pub fn responses(&self) -> &ToolResponses {
&self.typ
}
@@ -565,6 +574,8 @@ fn format_tool_response_for_ai(txn: &Transaction, ndb: &Ndb, resp: &ToolResponse
serde_json::to_string(&json!({"search_results": simple_notes})).unwrap()
}
+
+ ToolResponses::ExecutedTool(r) => format!("{}: {}", r.tool_name, r.summary),
}
}
diff --git a/crates/notedeck_dave/src/ui/dave.rs b/crates/notedeck_dave/src/ui/dave.rs
@@ -9,12 +9,12 @@ use crate::{
file_update::FileUpdate,
git_status::GitStatusCache,
messages::{
- AskUserQuestionInput, AssistantMessage, CompactionInfo, Message, PermissionRequest,
- PermissionResponse, PermissionResponseType, QuestionAnswer, SubagentInfo, SubagentStatus,
- ToolResult,
+ AskUserQuestionInput, AssistantMessage, CompactionInfo, ExecutedTool, Message,
+ PermissionRequest, PermissionResponse, PermissionResponseType, QuestionAnswer,
+ SubagentInfo, SubagentStatus,
},
session::{PermissionMessageState, SessionDetails, SessionId},
- tools::{PresentNotesCall, ToolCall, ToolCalls, ToolResponse},
+ tools::{PresentNotesCall, ToolCall, ToolCalls, ToolResponse, ToolResponses},
};
use bitflags::bitflags;
use egui::{Align, Key, KeyboardShortcut, Layout, Modifiers};
@@ -402,7 +402,7 @@ impl<'a> DaveUi<'a> {
self.assistant_chat(msg, ui);
}
Message::ToolResponse(msg) => {
- Self::tool_response_ui(msg, ui);
+ Self::tool_response_ui(msg, is_agentic, ui);
}
Message::System(_msg) => {
// system prompt is not rendered. Maybe we could
@@ -421,12 +421,6 @@ impl<'a> DaveUi<'a> {
}
}
}
- Message::ToolResult(result) => {
- // Tool results only in Agentic mode
- if is_agentic {
- Self::tool_result_ui(result, ui);
- }
- }
Message::CompactionComplete(info) => {
// Compaction only in Agentic mode
if is_agentic {
@@ -473,8 +467,17 @@ impl<'a> DaveUi<'a> {
response
}
- fn tool_response_ui(_tool_response: &ToolResponse, _ui: &mut egui::Ui) {
- //ui.label(format!("tool_response: {:?}", tool_response));
+ fn tool_response_ui(tool_response: &ToolResponse, is_agentic: bool, ui: &mut egui::Ui) {
+ match tool_response.responses() {
+ ToolResponses::ExecutedTool(result) => {
+ if is_agentic {
+ Self::executed_tool_ui(result, ui);
+ }
+ }
+ _ => {
+ //ui.label(format!("tool_response: {:?}", tool_response));
+ }
+ }
}
/// Render a permission request with Allow/Deny buttons or response state
@@ -828,7 +831,7 @@ impl<'a> DaveUi<'a> {
}
/// Render tool result metadata as a compact line
- fn tool_result_ui(result: &ToolResult, ui: &mut egui::Ui) {
+ fn executed_tool_ui(result: &ExecutedTool, ui: &mut egui::Ui) {
// Compact single-line display with subdued styling
ui.horizontal(|ui| {
// Tool name in slightly brighter text
@@ -924,7 +927,7 @@ impl<'a> DaveUi<'a> {
if expanded {
ui.indent(("subagent_tools", &info.task_id), |ui| {
for result in &info.tool_results {
- Self::tool_result_ui(result, ui);
+ Self::executed_tool_ui(result, ui);
}
});
}