Skip to main content

apollo/tools/
message.rs

1//! Message tool — send messages via Telegram (like OpenClaw's message tool).
2//! Allows the AI to proactively send messages, edit, delete.
3
4use async_trait::async_trait;
5use serde::Deserialize;
6use std::sync::Arc;
7
8use super::traits::*;
9use crate::channels::telegram::TelegramChannel;
10
11pub struct MessageTool {
12    tg: Arc<TelegramChannel>,
13}
14
15impl MessageTool {
16    pub fn new(tg: Arc<TelegramChannel>) -> Self {
17        Self { tg }
18    }
19}
20
21#[derive(Deserialize)]
22struct MessageArgs {
23    /// Action: send, edit, delete
24    action: String,
25    /// Message text (for send/edit)
26    message: Option<String>,
27    /// Message ID (for edit/delete)
28    message_id: Option<i64>,
29}
30
31#[async_trait]
32impl Tool for MessageTool {
33    fn name(&self) -> &str {
34        "message"
35    }
36
37    fn spec(&self) -> ToolSpec {
38        ToolSpec {
39            name: "message".to_string(),
40            description: "Send, edit, or delete Telegram messages. Actions: send, edit, delete."
41                .to_string(),
42            parameters: serde_json::json!({
43                "type": "object",
44                "properties": {
45                    "action": {
46                        "type": "string",
47                        "enum": ["send", "edit", "delete"],
48                        "description": "Message action"
49                    },
50                    "message": {
51                        "type": "string",
52                        "description": "Message text (for send/edit)"
53                    },
54                    "message_id": {
55                        "type": "integer",
56                        "description": "Message ID (for edit/delete)"
57                    }
58                },
59                "required": ["action"]
60            }),
61        }
62    }
63
64    async fn execute(&self, arguments: &str) -> anyhow::Result<ToolResult> {
65        let args: MessageArgs = serde_json::from_str(arguments)?;
66
67        match args.action.as_str() {
68            "send" => {
69                let text = args.message.unwrap_or_default();
70                if text.is_empty() {
71                    return Ok(ToolResult::error("Message text required for send action"));
72                }
73                let msg_id = self.tg.send_message(&text).await?;
74                Ok(ToolResult::success(format!(
75                    "Sent message (id: {})",
76                    msg_id
77                )))
78            }
79            "edit" => {
80                let msg_id = args.message_id.unwrap_or(0);
81                let text = args.message.unwrap_or_default();
82                if msg_id == 0 || text.is_empty() {
83                    return Ok(ToolResult::error(
84                        "message_id and message required for edit action",
85                    ));
86                }
87                self.tg.edit_message(msg_id, &text).await?;
88                Ok(ToolResult::success(format!("Edited message {}", msg_id)))
89            }
90            "delete" => {
91                let msg_id = args.message_id.unwrap_or(0);
92                if msg_id == 0 {
93                    return Ok(ToolResult::error("message_id required for delete action"));
94                }
95                self.tg.delete_message(msg_id).await?;
96                Ok(ToolResult::success(format!("Deleted message {}", msg_id)))
97            }
98            other => Ok(ToolResult::error(format!("Unknown action: {}", other))),
99        }
100    }
101}