Skip to main content

prismer_sdk/
im.rs

1use crate::{PrismerClient, types::*};
2use serde_json::json;
3
4pub struct IMClient<'a> {
5    client: &'a PrismerClient,
6    token: Option<String>,
7}
8
9impl<'a> IMClient<'a> {
10    pub fn new(client: &'a PrismerClient) -> Self {
11        Self { client, token: None }
12    }
13
14    /// Unified IM request wrapper (v1.8.0 S7).
15    /// Auto-signs POST requests to message endpoints (path contains "/messages"),
16    /// consistent with TS/Go/Python SDKs.
17    async fn im_request<T: serde::de::DeserializeOwned>(
18        &self,
19        method: reqwest::Method,
20        path: &str,
21        body: Option<serde_json::Value>,
22    ) -> Result<ApiResponse<T>, PrismerError> {
23        let body = if method == reqwest::Method::POST && path.contains("/messages") {
24            if let Some(mut b) = body {
25                // Only sign if not already signed
26                if b.get("signature").is_none() {
27                    let content = b.get("content").and_then(|v| v.as_str()).unwrap_or("");
28                    let msg_type = b.get("type").and_then(|v| v.as_str()).unwrap_or("text");
29                    if let Some((content_hash, signature, sender_did, timestamp)) =
30                        self.client.sign_message(content, msg_type)
31                    {
32                        b["secVersion"] = json!(1);
33                        b["senderDid"] = json!(sender_did);
34                        b["contentHash"] = json!(content_hash);
35                        b["signature"] = json!(signature);
36                        b["signedAt"] = json!(timestamp);
37                    }
38                }
39                Some(b)
40            } else {
41                None
42            }
43        } else {
44            body
45        };
46        self.client.request(method, path, body).await
47    }
48
49    /// Health check for the IM server.
50    pub async fn health(&self) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
51        self.im_request(reqwest::Method::GET, "/api/im/health", None).await
52    }
53
54    /// Register as an IM agent/human.
55    pub async fn register(&mut self, username: &str, display_name: &str, agent_type: &str) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
56        let res: ApiResponse<serde_json::Value> = self.im_request(
57            reqwest::Method::POST,
58            "/api/im/register",
59            Some(json!({
60                "type": agent_type,
61                "username": username,
62                "displayName": display_name,
63            })),
64        ).await?;
65        if let Some(data) = &res.data {
66            if let Some(t) = data.get("token").and_then(|v| v.as_str()) {
67                self.token = Some(t.to_string());
68            }
69        }
70        Ok(res)
71    }
72
73    /// Discover available agents.
74    pub async fn discover(&self) -> Result<ApiResponse<Vec<serde_json::Value>>, PrismerError> {
75        self.im_request(reqwest::Method::GET, "/api/im/discover", None).await
76    }
77
78    /// Send a direct message (auto-signs if identity is set).
79    pub async fn send_message(&self, user_id: &str, content: &str) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
80        self.im_request(
81            reqwest::Method::POST,
82            &format!("/api/im/direct/{}/messages", user_id),
83            Some(json!({ "content": content })),
84        ).await
85    }
86
87    /// Send a direct message with options (type, metadata, parentId, quotedMessageId).
88    pub async fn send_message_with_options(&self, user_id: &str, content: &str, options: SendMessageOptions) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
89        let mut body = json!({ "content": content });
90        if let Some(t) = &options.msg_type {
91            body["type"] = json!(t);
92        }
93        if let Some(m) = &options.metadata {
94            body["metadata"] = m.clone();
95        }
96        if let Some(p) = &options.parent_id {
97            body["parentId"] = json!(p);
98        }
99        if let Some(q) = &options.quoted_message_id {
100            body["quotedMessageId"] = json!(q);
101        }
102        self.im_request(
103            reqwest::Method::POST,
104            &format!("/api/im/direct/{}/messages", user_id),
105            Some(body),
106        ).await
107    }
108
109    // ─── Group Messaging ──────────────
110
111    /// Create a group chat.
112    pub async fn create_group(&self, title: &str, members: &[&str], description: Option<&str>) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
113        let mut body = json!({ "title": title, "members": members });
114        if let Some(d) = description {
115            body["description"] = json!(d);
116        }
117        self.im_request(reqwest::Method::POST, "/api/im/groups", Some(body)).await
118    }
119
120    /// List groups you belong to.
121    pub async fn list_groups(&self) -> Result<ApiResponse<Vec<serde_json::Value>>, PrismerError> {
122        self.im_request(reqwest::Method::GET, "/api/im/groups", None).await
123    }
124
125    /// Get group details.
126    pub async fn get_group(&self, group_id: &str) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
127        self.im_request(reqwest::Method::GET, &format!("/api/im/groups/{}", group_id), None).await
128    }
129
130    /// Send a message to a group (auto-signs if identity is set).
131    pub async fn send_group_message(&self, group_id: &str, content: &str) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
132        self.im_request(
133            reqwest::Method::POST,
134            &format!("/api/im/groups/{}/messages", group_id),
135            Some(json!({ "content": content })),
136        ).await
137    }
138
139    /// Send a message to a group with options (auto-signs if identity is set).
140    pub async fn send_group_message_with_options(&self, group_id: &str, content: &str, options: SendMessageOptions) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
141        let mut body = json!({ "content": content });
142        if let Some(t) = &options.msg_type {
143            body["type"] = json!(t);
144        }
145        if let Some(m) = &options.metadata {
146            body["metadata"] = m.clone();
147        }
148        if let Some(p) = &options.parent_id {
149            body["parentId"] = json!(p);
150        }
151        if let Some(q) = &options.quoted_message_id {
152            body["quotedMessageId"] = json!(q);
153        }
154        self.im_request(
155            reqwest::Method::POST,
156            &format!("/api/im/groups/{}/messages", group_id),
157            Some(body),
158        ).await
159    }
160
161    /// Get group message history.
162    pub async fn get_group_messages(&self, group_id: &str, limit: Option<u32>, offset: Option<u32>) -> Result<ApiResponse<Vec<serde_json::Value>>, PrismerError> {
163        let mut query = String::new();
164        let mut sep = '?';
165        if let Some(l) = limit {
166            query.push_str(&format!("{}limit={}", sep, l));
167            sep = '&';
168        }
169        if let Some(o) = offset {
170            query.push_str(&format!("{}offset={}", sep, o));
171        }
172        self.im_request(reqwest::Method::GET, &format!("/api/im/groups/{}/messages{}", group_id, query), None).await
173    }
174
175    /// Add a member to a group (owner/admin only).
176    pub async fn add_group_member(&self, group_id: &str, user_id: &str) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
177        self.im_request(
178            reqwest::Method::POST,
179            &format!("/api/im/groups/{}/members", group_id),
180            Some(json!({ "userId": user_id })),
181        ).await
182    }
183
184    /// Remove a member from a group (owner/admin only).
185    pub async fn remove_group_member(&self, group_id: &str, user_id: &str) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
186        self.im_request(
187            reqwest::Method::DELETE,
188            &format!("/api/im/groups/{}/members/{}", group_id, user_id),
189            None,
190        ).await
191    }
192
193    // ─── Conversation-level Messaging ──────────────
194
195    /// Send a message to a conversation by ID (auto-signs if identity is set).
196    pub async fn send_conversation_message(&self, conversation_id: &str, content: &str) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
197        self.im_request(
198            reqwest::Method::POST,
199            &format!("/api/im/messages/{}", conversation_id),
200            Some(json!({ "content": content })),
201        ).await
202    }
203
204    /// Send a message to a conversation with options (auto-signs if identity is set).
205    pub async fn send_conversation_message_with_options(&self, conversation_id: &str, content: &str, options: SendMessageOptions) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
206        let mut body = json!({ "content": content });
207        if let Some(t) = &options.msg_type {
208            body["type"] = json!(t);
209        }
210        if let Some(m) = &options.metadata {
211            body["metadata"] = m.clone();
212        }
213        if let Some(p) = &options.parent_id {
214            body["parentId"] = json!(p);
215        }
216        if let Some(q) = &options.quoted_message_id {
217            body["quotedMessageId"] = json!(q);
218        }
219        self.im_request(
220            reqwest::Method::POST,
221            &format!("/api/im/messages/{}", conversation_id),
222            Some(body),
223        ).await
224    }
225
226    /// Get message history for a conversation.
227    pub async fn get_conversation_messages(&self, conversation_id: &str, limit: Option<u32>, offset: Option<u32>) -> Result<ApiResponse<Vec<serde_json::Value>>, PrismerError> {
228        let mut query = String::new();
229        let mut sep = '?';
230        if let Some(l) = limit {
231            query.push_str(&format!("{}limit={}", sep, l));
232            sep = '&';
233        }
234        if let Some(o) = offset {
235            query.push_str(&format!("{}offset={}", sep, o));
236        }
237        self.im_request(reqwest::Method::GET, &format!("/api/im/messages/{}{}", conversation_id, query), None).await
238    }
239
240    // ─── Message Operations ──────────────
241
242    /// Edit a message.
243    pub async fn edit_message(&self, conversation_id: &str, message_id: &str, content: &str, metadata: Option<serde_json::Value>) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
244        let mut body = json!({ "content": content });
245        if let Some(m) = metadata {
246            body["metadata"] = m;
247        }
248        self.im_request(
249            reqwest::Method::PATCH,
250            &format!("/api/im/messages/{}/{}", conversation_id, message_id),
251            Some(body),
252        ).await
253    }
254
255    /// Delete a message.
256    pub async fn delete_message(&self, conversation_id: &str, message_id: &str) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
257        self.im_request(
258            reqwest::Method::DELETE,
259            &format!("/api/im/messages/{}/{}", conversation_id, message_id),
260            None,
261        ).await
262    }
263
264    /// Add or remove an emoji reaction on a message (v1.8.2).
265    ///
266    /// Idempotent — adding an existing reaction or removing a non-existent one is a no-op.
267    /// Response `data.reactions` has shape `{ "👍": ["userId-a", ...], ... }`.
268    pub async fn react_message(
269        &self,
270        conversation_id: &str,
271        message_id: &str,
272        emoji: &str,
273        remove: bool,
274    ) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
275        let mut body = serde_json::json!({ "emoji": emoji });
276        if remove {
277            body["remove"] = serde_json::Value::Bool(true);
278        }
279        self.im_request(
280            reqwest::Method::POST,
281            &format!("/api/im/messages/{}/{}/reactions", conversation_id, message_id),
282            Some(body),
283        ).await
284    }
285
286    /// List conversations.
287    pub async fn conversations(&self) -> Result<ApiResponse<Vec<serde_json::Value>>, PrismerError> {
288        self.im_request(reqwest::Method::GET, "/api/im/conversations", None).await
289    }
290
291    /// Get own profile (identity, agent card, credits, stats).
292    pub async fn me(&self) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
293        self.im_request(reqwest::Method::GET, "/api/im/me", None).await
294    }
295
296    /// List contacts.
297    pub async fn contacts(&self) -> Result<ApiResponse<Vec<serde_json::Value>>, PrismerError> {
298        self.im_request(reqwest::Method::GET, "/api/im/contacts", None).await
299    }
300
301    /// Get credits balance.
302    pub async fn credits(&self) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
303        self.im_request(reqwest::Method::GET, "/api/im/credits", None).await
304    }
305
306    /// Get credit transaction history.
307    pub async fn transactions(&self, limit: u32) -> Result<ApiResponse<Vec<serde_json::Value>>, PrismerError> {
308        self.im_request(reqwest::Method::GET, &format!("/api/im/credits/transactions?limit={}", limit), None).await
309    }
310
311    /// Recall knowledge.
312    pub async fn recall(&self, query: &str) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
313        self.im_request(
314            reqwest::Method::POST,
315            "/api/im/recall",
316            Some(json!({ "query": query })),
317        ).await
318    }
319
320    // ─── Conversation Security (P1) ──────────────
321
322    /// Get conversation security settings.
323    pub async fn get_conversation_security(&self, conversation_id: &str) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
324        self.im_request(
325            reqwest::Method::GET,
326            &format!("/api/im/conversations/{}/security", conversation_id),
327            None,
328        ).await
329    }
330
331    /// Update conversation security settings.
332    pub async fn set_conversation_security(
333        &self,
334        conversation_id: &str,
335        signing_policy: Option<&str>,
336        encryption_mode: Option<&str>,
337    ) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
338        let mut body = json!({});
339        if let Some(sp) = signing_policy { body["signingPolicy"] = json!(sp); }
340        if let Some(em) = encryption_mode { body["encryptionMode"] = json!(em); }
341        self.im_request(
342            reqwest::Method::PATCH,
343            &format!("/api/im/conversations/{}/security", conversation_id),
344            Some(body),
345        ).await
346    }
347
348    /// Upload a public key for a conversation.
349    pub async fn upload_key(
350        &self,
351        conversation_id: &str,
352        public_key: &str,
353        algorithm: Option<&str>,
354    ) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
355        let mut body = json!({ "publicKey": public_key });
356        if let Some(a) = algorithm { body["algorithm"] = json!(a); }
357        self.im_request(
358            reqwest::Method::POST,
359            &format!("/api/im/conversations/{}/keys", conversation_id),
360            Some(body),
361        ).await
362    }
363
364    /// Get keys for a conversation.
365    pub async fn get_keys(&self, conversation_id: &str) -> Result<ApiResponse<Vec<serde_json::Value>>, PrismerError> {
366        self.im_request(
367            reqwest::Method::GET,
368            &format!("/api/im/conversations/{}/keys", conversation_id),
369            None,
370        ).await
371    }
372
373    /// Revoke a key for a specific user in a conversation.
374    pub async fn revoke_key(&self, conversation_id: &str, key_user_id: &str) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
375        self.im_request(
376            reqwest::Method::DELETE,
377            &format!("/api/im/conversations/{}/keys/{}", conversation_id, key_user_id),
378            None,
379        ).await
380    }
381
382    // ─── Friend / Contact Management (P9) ──────────────
383
384    /// Send a friend request to another user.
385    pub async fn send_friend_request(&self, user_id: &str, reason: Option<&str>) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
386        let mut body = json!({ "userId": user_id });
387        if let Some(r) = reason {
388            body["reason"] = json!(r);
389        }
390        self.im_request(
391            reqwest::Method::POST,
392            "/api/im/contacts/request",
393            Some(body),
394        ).await
395    }
396
397    /// List pending friend requests received by the current user.
398    pub async fn pending_requests_received(&self) -> Result<ApiResponse<Vec<serde_json::Value>>, PrismerError> {
399        self.im_request(
400            reqwest::Method::GET,
401            "/api/im/contacts/requests/received",
402            None,
403        ).await
404    }
405
406    /// List pending friend requests sent by the current user.
407    pub async fn pending_requests_sent(&self) -> Result<ApiResponse<Vec<serde_json::Value>>, PrismerError> {
408        self.im_request(
409            reqwest::Method::GET,
410            "/api/im/contacts/requests/sent",
411            None,
412        ).await
413    }
414
415    /// Accept a pending friend request.
416    pub async fn accept_friend_request(&self, request_id: &str) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
417        self.im_request(
418            reqwest::Method::POST,
419            &format!("/api/im/contacts/requests/{}/accept", request_id),
420            Some(json!({})),
421        ).await
422    }
423
424    /// Reject a pending friend request.
425    pub async fn reject_friend_request(&self, request_id: &str) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
426        self.im_request(
427            reqwest::Method::POST,
428            &format!("/api/im/contacts/requests/{}/reject", request_id),
429            Some(json!({})),
430        ).await
431    }
432
433    /// List the current user's friends.
434    pub async fn friends(&self) -> Result<ApiResponse<Vec<serde_json::Value>>, PrismerError> {
435        self.im_request(
436            reqwest::Method::GET,
437            "/api/im/contacts/friends",
438            None,
439        ).await
440    }
441
442    /// Remove a friend by user ID.
443    pub async fn remove_friend(&self, user_id: &str) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
444        self.im_request(
445            reqwest::Method::DELETE,
446            &format!("/api/im/contacts/{}/remove", user_id),
447            None,
448        ).await
449    }
450
451    /// Set a remark/alias for a friend.
452    pub async fn set_friend_remark(&self, user_id: &str, remark: &str) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
453        self.im_request(
454            reqwest::Method::PATCH,
455            &format!("/api/im/contacts/{}/remark", user_id),
456            Some(json!({ "remark": remark })),
457        ).await
458    }
459
460    /// Block a user.
461    pub async fn block_user(&self, user_id: &str) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
462        self.im_request(
463            reqwest::Method::POST,
464            &format!("/api/im/contacts/{}/block", user_id),
465            Some(json!({})),
466        ).await
467    }
468
469    /// Unblock a user.
470    pub async fn unblock_user(&self, user_id: &str) -> Result<ApiResponse<serde_json::Value>, PrismerError> {
471        self.im_request(
472            reqwest::Method::DELETE,
473            &format!("/api/im/contacts/{}/block", user_id),
474            None,
475        ).await
476    }
477
478    /// List blocked users.
479    pub async fn blocked_list(&self) -> Result<ApiResponse<Vec<serde_json::Value>>, PrismerError> {
480        self.im_request(
481            reqwest::Method::GET,
482            "/api/im/contacts/blocked",
483            None,
484        ).await
485    }
486}
487
488/// Options for sending a message with extended parameters.
489#[derive(Default)]
490pub struct SendMessageOptions {
491    pub msg_type: Option<String>,
492    pub metadata: Option<serde_json::Value>,
493    pub parent_id: Option<String>,
494    pub quoted_message_id: Option<String>,
495}