notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

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 }