synaptic_lark/tools/
message.rs1use async_trait::async_trait;
2use serde_json::{json, Value};
3use synaptic_core::{SynapticError, Tool};
4
5use crate::{api::message::MessageApi, LarkConfig};
6
7pub struct LarkMessageTool {
53 api: MessageApi,
54}
55
56impl LarkMessageTool {
57 pub fn new(config: LarkConfig) -> Self {
59 Self {
60 api: MessageApi::new(config),
61 }
62 }
63}
64
65#[async_trait]
66impl Tool for LarkMessageTool {
67 fn name(&self) -> &'static str {
68 "lark_send_message"
69 }
70
71 fn description(&self) -> &'static str {
72 "Send, update, or delete a Feishu/Lark message. \
73 Use action='send' (default) to send to a chat or user; \
74 action='update' to patch an existing message; \
75 action='delete' to recall a message."
76 }
77
78 fn parameters(&self) -> Option<Value> {
79 Some(json!({
80 "type": "object",
81 "properties": {
82 "action": {
83 "type": "string",
84 "description": "Operation: send (default) | update | delete",
85 "enum": ["send", "update", "delete"]
86 },
87 "receive_id_type": {
88 "type": "string",
89 "description": "For 'send': type of receiver ID: chat_id | user_id | email | open_id",
90 "enum": ["chat_id", "user_id", "email", "open_id"]
91 },
92 "receive_id": {
93 "type": "string",
94 "description": "For 'send': the receiver ID matching receive_id_type"
95 },
96 "msg_type": {
97 "type": "string",
98 "description": "For 'send'/'update': message type: text | post | interactive",
99 "enum": ["text", "post", "interactive"]
100 },
101 "content": {
102 "type": "string",
103 "description": "For 'send'/'update': message content. For text: plain string. For post/interactive: JSON string."
104 },
105 "message_id": {
106 "type": "string",
107 "description": "For 'update'/'delete': the message ID (om_xxx)"
108 }
109 },
110 "required": ["action"]
111 }))
112 }
113
114 async fn call(&self, args: Value) -> Result<Value, SynapticError> {
115 let action = args
116 .get("action")
117 .and_then(|v| v.as_str())
118 .unwrap_or("send");
119
120 match action {
121 "send" => {
122 let receive_id_type = args["receive_id_type"]
123 .as_str()
124 .ok_or_else(|| SynapticError::Tool("missing 'receive_id_type'".to_string()))?;
125 let receive_id = args["receive_id"]
126 .as_str()
127 .ok_or_else(|| SynapticError::Tool("missing 'receive_id'".to_string()))?;
128 let msg_type = args["msg_type"]
129 .as_str()
130 .ok_or_else(|| SynapticError::Tool("missing 'msg_type'".to_string()))?;
131 let content = args["content"]
132 .as_str()
133 .ok_or_else(|| SynapticError::Tool("missing 'content'".to_string()))?;
134
135 let content_json = build_content_json(msg_type, content)?;
136 let message_id = self
137 .api
138 .send(receive_id_type, receive_id, msg_type, &content_json)
139 .await?;
140 tracing::debug!("Lark message sent: {message_id}");
141 Ok(json!({ "message_id": message_id, "status": "sent" }))
142 }
143
144 "update" => {
145 let message_id = args["message_id"]
146 .as_str()
147 .ok_or_else(|| SynapticError::Tool("missing 'message_id'".to_string()))?;
148 let msg_type = args["msg_type"]
149 .as_str()
150 .ok_or_else(|| SynapticError::Tool("missing 'msg_type'".to_string()))?;
151 let content = args["content"]
152 .as_str()
153 .ok_or_else(|| SynapticError::Tool("missing 'content'".to_string()))?;
154
155 let content_json = build_content_json(msg_type, content)?;
156 self.api.update(message_id, msg_type, &content_json).await?;
157 Ok(json!({ "message_id": message_id, "status": "updated" }))
158 }
159
160 "delete" => {
161 let message_id = args["message_id"]
162 .as_str()
163 .ok_or_else(|| SynapticError::Tool("missing 'message_id'".to_string()))?;
164 self.api.delete(message_id).await?;
165 Ok(json!({ "message_id": message_id, "status": "deleted" }))
166 }
167
168 other => Err(SynapticError::Tool(format!(
169 "unknown action '{other}': expected send | update | delete"
170 ))),
171 }
172 }
173}
174
175fn build_content_json(msg_type: &str, content: &str) -> Result<String, SynapticError> {
180 match msg_type {
181 "text" => Ok(json!({"text": content}).to_string()),
182 _ => {
183 serde_json::from_str::<Value>(content)
184 .map_err(|e| {
185 SynapticError::Tool(format!(
186 "content is not valid JSON for msg_type='{msg_type}': {e}"
187 ))
188 })?
189 .to_string();
190 Ok(content.to_string())
191 }
192 }
193}