messages.rs (8672B)
1 use crate::tools::{ToolCall, ToolResponse}; 2 use async_openai::types::*; 3 use nostrdb::{Ndb, Transaction}; 4 use serde::{Deserialize, Serialize}; 5 use tokio::sync::oneshot; 6 use uuid::Uuid; 7 8 /// A question option from AskUserQuestion 9 #[derive(Debug, Clone, Deserialize)] 10 pub struct QuestionOption { 11 pub label: String, 12 pub description: String, 13 } 14 15 /// A single question from AskUserQuestion 16 #[derive(Debug, Clone, Deserialize)] 17 #[serde(rename_all = "camelCase")] 18 pub struct UserQuestion { 19 pub question: String, 20 pub header: String, 21 #[serde(default)] 22 pub multi_select: bool, 23 pub options: Vec<QuestionOption>, 24 } 25 26 /// Parsed AskUserQuestion tool input 27 #[derive(Debug, Clone, Deserialize)] 28 pub struct AskUserQuestionInput { 29 pub questions: Vec<UserQuestion>, 30 } 31 32 /// User's answer to a question 33 #[derive(Debug, Clone, Default, Serialize)] 34 pub struct QuestionAnswer { 35 /// Selected option indices 36 pub selected: Vec<usize>, 37 /// Custom "Other" text if provided 38 pub other_text: Option<String>, 39 } 40 41 /// A request for user permission to use a tool (displayable data only) 42 #[derive(Debug, Clone)] 43 pub struct PermissionRequest { 44 /// Unique identifier for this permission request 45 pub id: Uuid, 46 /// The tool that wants to be used 47 pub tool_name: String, 48 /// The arguments the tool will be called with 49 pub tool_input: serde_json::Value, 50 /// The user's response (None if still pending) 51 pub response: Option<PermissionResponseType>, 52 /// For AskUserQuestion: pre-computed summary of answers for display 53 pub answer_summary: Option<AnswerSummary>, 54 } 55 56 /// A single entry in an answer summary 57 #[derive(Debug, Clone)] 58 pub struct AnswerSummaryEntry { 59 /// The question header (e.g., "Library", "Approach") 60 pub header: String, 61 /// The selected answer text, comma-separated if multiple 62 pub answer: String, 63 } 64 65 /// Pre-computed summary of an AskUserQuestion response for display 66 #[derive(Debug, Clone)] 67 pub struct AnswerSummary { 68 pub entries: Vec<AnswerSummaryEntry>, 69 } 70 71 /// A permission request with the response channel (for channel communication) 72 pub struct PendingPermission { 73 /// The displayable request data 74 pub request: PermissionRequest, 75 /// Channel to send the user's response back 76 pub response_tx: oneshot::Sender<PermissionResponse>, 77 } 78 79 /// The user's response to a permission request 80 #[derive(Debug, Clone)] 81 pub enum PermissionResponse { 82 /// Allow the tool to execute, with an optional message for the AI 83 Allow { message: Option<String> }, 84 /// Deny the tool execution with a reason 85 Deny { reason: String }, 86 } 87 88 /// The recorded response type for display purposes (without channel details) 89 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 90 pub enum PermissionResponseType { 91 Allowed, 92 Denied, 93 } 94 95 /// Metadata about a completed tool execution 96 #[derive(Debug, Clone)] 97 pub struct ToolResult { 98 pub tool_name: String, 99 pub summary: String, // e.g., "154 lines", "exit 0", "3 matches" 100 } 101 102 /// Session initialization info from Claude Code CLI 103 #[derive(Debug, Clone, Default)] 104 pub struct SessionInfo { 105 /// Available tools in this session 106 pub tools: Vec<String>, 107 /// Model being used (e.g., "claude-opus-4-5-20251101") 108 pub model: Option<String>, 109 /// Permission mode (e.g., "default", "plan") 110 pub permission_mode: Option<String>, 111 /// Available slash commands 112 pub slash_commands: Vec<String>, 113 /// Available agent types for Task tool 114 pub agents: Vec<String>, 115 /// Claude Code CLI version 116 pub cli_version: Option<String>, 117 /// Current working directory 118 pub cwd: Option<String>, 119 /// Session ID from Claude Code 120 pub claude_session_id: Option<String>, 121 } 122 123 /// Status of a subagent spawned by the Task tool 124 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 125 pub enum SubagentStatus { 126 /// Subagent is running 127 Running, 128 /// Subagent completed successfully 129 Completed, 130 /// Subagent failed with an error 131 Failed, 132 } 133 134 /// Information about a subagent spawned by the Task tool 135 #[derive(Debug, Clone)] 136 pub struct SubagentInfo { 137 /// Unique ID for this subagent task 138 pub task_id: String, 139 /// Description of what the subagent is doing 140 pub description: String, 141 /// Type of subagent (e.g., "Explore", "Plan", "Bash") 142 pub subagent_type: String, 143 /// Current status 144 pub status: SubagentStatus, 145 /// Output content (truncated for display) 146 pub output: String, 147 /// Maximum output size to keep (for size-restricted window) 148 pub max_output_size: usize, 149 } 150 151 #[derive(Debug, Clone)] 152 pub enum Message { 153 System(String), 154 Error(String), 155 User(String), 156 Assistant(String), 157 ToolCalls(Vec<ToolCall>), 158 ToolResponse(ToolResponse), 159 /// A permission request from the AI that needs user response 160 PermissionRequest(PermissionRequest), 161 /// Result metadata from a completed tool execution 162 ToolResult(ToolResult), 163 /// Conversation was compacted 164 CompactionComplete(CompactionInfo), 165 /// A subagent spawned by Task tool 166 Subagent(SubagentInfo), 167 } 168 169 /// Compaction info from compact_boundary system message 170 #[derive(Debug, Clone)] 171 pub struct CompactionInfo { 172 /// Number of tokens before compaction 173 pub pre_tokens: u64, 174 } 175 176 /// The ai backends response. Since we are using streaming APIs these are 177 /// represented as individual tokens or tool calls 178 pub enum DaveApiResponse { 179 ToolCalls(Vec<ToolCall>), 180 Token(String), 181 Failed(String), 182 /// A permission request that needs to be displayed to the user 183 PermissionRequest(PendingPermission), 184 /// Metadata from a completed tool execution 185 ToolResult(ToolResult), 186 /// Session initialization info from Claude Code CLI 187 SessionInfo(SessionInfo), 188 /// Subagent spawned by Task tool 189 SubagentSpawned(SubagentInfo), 190 /// Subagent output update 191 SubagentOutput { 192 task_id: String, 193 output: String, 194 }, 195 /// Subagent completed 196 SubagentCompleted { 197 task_id: String, 198 result: String, 199 }, 200 /// Conversation compaction started 201 CompactionStarted, 202 /// Conversation compaction completed with token info 203 CompactionComplete(CompactionInfo), 204 } 205 206 impl Message { 207 pub fn tool_error(id: String, msg: String) -> Self { 208 Self::ToolResponse(ToolResponse::error(id, msg)) 209 } 210 211 pub fn to_api_msg(&self, txn: &Transaction, ndb: &Ndb) -> Option<ChatCompletionRequestMessage> { 212 match self { 213 Message::Error(_err) => None, 214 215 Message::User(msg) => Some(ChatCompletionRequestMessage::User( 216 ChatCompletionRequestUserMessage { 217 name: None, 218 content: ChatCompletionRequestUserMessageContent::Text(msg.clone()), 219 }, 220 )), 221 222 Message::Assistant(msg) => Some(ChatCompletionRequestMessage::Assistant( 223 ChatCompletionRequestAssistantMessage { 224 content: Some(ChatCompletionRequestAssistantMessageContent::Text( 225 msg.clone(), 226 )), 227 ..Default::default() 228 }, 229 )), 230 231 Message::System(msg) => Some(ChatCompletionRequestMessage::System( 232 ChatCompletionRequestSystemMessage { 233 content: ChatCompletionRequestSystemMessageContent::Text(msg.clone()), 234 ..Default::default() 235 }, 236 )), 237 238 Message::ToolCalls(calls) => Some(ChatCompletionRequestMessage::Assistant( 239 ChatCompletionRequestAssistantMessage { 240 tool_calls: Some(calls.iter().map(|c| c.to_api()).collect()), 241 ..Default::default() 242 }, 243 )), 244 245 Message::ToolResponse(resp) => { 246 let tool_response = resp.responses().format_for_dave(txn, ndb); 247 248 Some(ChatCompletionRequestMessage::Tool( 249 ChatCompletionRequestToolMessage { 250 tool_call_id: resp.id().to_owned(), 251 content: ChatCompletionRequestToolMessageContent::Text(tool_response), 252 }, 253 )) 254 } 255 256 // Permission requests are UI-only, not sent to the API 257 Message::PermissionRequest(_) => None, 258 259 // Tool results are UI-only, not sent to the API 260 Message::ToolResult(_) => None, 261 262 // Compaction complete is UI-only, not sent to the API 263 Message::CompactionComplete(_) => None, 264 265 // Subagent info is UI-only, not sent to the API 266 Message::Subagent(_) => None, 267 } 268 } 269 }