Skip to main content

wechat_agent_rs/
models.rs

1use async_trait::async_trait;
2use serde::{Deserialize, Serialize};
3
4/// Trait that application code implements to handle incoming chat messages.
5///
6/// The SDK calls [`Agent::chat`] for every incoming message and sends the
7/// returned [`ChatResponse`] back to the `WeChat` user.
8#[async_trait]
9pub trait Agent: Send + Sync {
10    /// Processes an incoming chat request and returns a response.
11    async fn chat(&self, request: ChatRequest) -> crate::Result<ChatResponse>;
12}
13
14/// An incoming chat message delivered to the agent.
15#[derive(Debug, Clone)]
16pub struct ChatRequest {
17    /// Unique identifier for the conversation / user.
18    pub conversation_id: String,
19    /// The text body of the message (may be empty when media-only).
20    pub text:            String,
21    /// Optional media attachment included with the message.
22    pub media:           Option<IncomingMedia>,
23}
24
25/// The agent's reply to be sent back to the user.
26#[derive(Debug, Clone, Default)]
27pub struct ChatResponse {
28    /// Optional text content of the reply.
29    pub text:  Option<String>,
30    /// Optional media attachment to include in the reply.
31    pub media: Option<OutgoingMedia>,
32}
33
34/// A media file received from a `WeChat` user.
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct IncomingMedia {
37    /// The kind of media (image, audio, video, or generic file).
38    pub media_type: MediaType,
39    /// Local filesystem path to the downloaded and decrypted file.
40    pub file_path:  String,
41    /// MIME type of the file.
42    pub mime_type:  String,
43    /// Original file name, if available.
44    pub file_name:  Option<String>,
45}
46
47/// A media file to be sent to a `WeChat` user.
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct OutgoingMedia {
50    /// The kind of media to send.
51    pub media_type: OutgoingMediaType,
52    /// URL from which the SDK will download the file before uploading to
53    /// `WeChat`.
54    pub url:        String,
55    /// Optional file name hint for the uploaded file.
56    pub file_name:  Option<String>,
57}
58
59/// Classification of an incoming media attachment.
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
61#[serde(rename_all = "snake_case")]
62pub enum MediaType {
63    /// A still image (JPEG, PNG, etc.).
64    Image,
65    /// An audio recording or voice message.
66    Audio,
67    /// A video clip.
68    Video,
69    /// A generic file attachment.
70    File,
71}
72
73/// Classification of an outgoing media attachment.
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
75#[serde(rename_all = "snake_case")]
76pub enum OutgoingMediaType {
77    /// A still image.
78    Image,
79    /// A video clip.
80    Video,
81    /// A generic file.
82    File,
83}
84
85/// Options for the QR-code login flow.
86#[derive(Debug, Clone, Default)]
87pub struct LoginOptions {
88    /// Override the default API base URL.
89    pub base_url: Option<String>,
90}
91
92/// Options for starting the message-polling loop.
93#[derive(Debug, Clone, Default)]
94pub struct StartOptions {
95    /// Account ID to use; if `None`, the first saved account is used.
96    pub account_id: Option<String>,
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_chat_response_default() {
105        let resp = ChatResponse::default();
106        assert!(resp.text.is_none());
107        assert!(resp.media.is_none());
108    }
109
110    #[test]
111    fn test_media_type_serde() {
112        for variant in [
113            MediaType::Image,
114            MediaType::Audio,
115            MediaType::Video,
116            MediaType::File,
117        ] {
118            let json = serde_json::to_string(&variant).unwrap();
119            let deserialized: MediaType = serde_json::from_str(&json).unwrap();
120            assert_eq!(deserialized, variant);
121        }
122    }
123
124    #[test]
125    fn test_outgoing_media_type_serde() {
126        for variant in [
127            OutgoingMediaType::Image,
128            OutgoingMediaType::Video,
129            OutgoingMediaType::File,
130        ] {
131            let json = serde_json::to_string(&variant).unwrap();
132            let deserialized: OutgoingMediaType = serde_json::from_str(&json).unwrap();
133            assert_eq!(deserialized, variant);
134        }
135    }
136}