1use serde::{Deserialize, Serialize};
10use serde_json::Value;
11use std::collections::HashMap;
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct ListPromptsRequest {
16    #[serde(skip_serializing_if = "Option::is_none")]
18    pub cursor: Option<String>,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23pub struct ListPromptsResponse {
24    pub prompts: Vec<Prompt>,
26
27    #[serde(skip_serializing_if = "Option::is_none")]
29    pub next_cursor: Option<String>,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct Prompt {
35    pub name: String,
37
38    pub description: String,
40
41    #[serde(skip_serializing_if = "Option::is_none")]
43    pub arguments: Option<Value>,
44}
45
46impl Prompt {
47    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
49        Self {
50            name: name.into(),
51            description: description.into(),
52            arguments: None,
53        }
54    }
55
56    pub fn with_arguments(mut self, arguments: Value) -> Self {
58        self.arguments = Some(arguments);
59        self
60    }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65pub struct GetPromptRequest {
66    pub name: String,
68
69    #[serde(skip_serializing_if = "Option::is_none")]
71    pub arguments: Option<Value>,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
76pub struct GetPromptResponse {
77    #[serde(skip_serializing_if = "Option::is_none")]
79    pub description: Option<String>,
80
81    #[serde(default)]
83    pub messages: Vec<PromptMessage>,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88pub struct PromptMessage {
89    pub role: MessageRole,
91
92    pub content: PromptContent,
94}
95
96impl PromptMessage {
97    pub fn new(role: MessageRole, content: PromptContent) -> Self {
99        Self { role, content }
100    }
101
102    pub fn system(content: impl Into<String>) -> Self {
104        Self::new(MessageRole::System, PromptContent::text(content))
105    }
106
107    pub fn user(content: impl Into<String>) -> Self {
109        Self::new(MessageRole::User, PromptContent::text(content))
110    }
111
112    pub fn assistant(content: impl Into<String>) -> Self {
114        Self::new(MessageRole::Assistant, PromptContent::text(content))
115    }
116}
117
118#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
120#[serde(rename_all = "lowercase")]
121pub enum MessageRole {
122    System,
124    User,
126    Assistant,
128}
129
130#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
132#[serde(tag = "type")]
133pub enum PromptContent {
134    #[serde(rename = "text")]
136    Text {
137        text: String,
139    },
140
141    #[serde(rename = "image")]
143    Image {
144        data: String,
146
147        #[serde(rename = "mimeType")]
149        mime_type: String,
150    },
151
152    #[serde(rename = "resource")]
154    Resource {
155        resource: ResourceReference,
157    },
158}
159
160impl PromptContent {
161    pub fn text(text: impl Into<String>) -> Self {
163        Self::Text { text: text.into() }
164    }
165
166    pub fn image(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
168        Self::Image {
169            data: data.into(),
170            mime_type: mime_type.into(),
171        }
172    }
173
174    pub fn resource(uri: impl Into<String>) -> Self {
176        Self::Resource {
177            resource: ResourceReference {
178                uri: uri.into(),
179                text: None,
180            },
181        }
182    }
183
184    pub fn resource_with_text(uri: impl Into<String>, text: impl Into<String>) -> Self {
186        Self::Resource {
187            resource: ResourceReference {
188                uri: uri.into(),
189                text: Some(text.into()),
190            },
191        }
192    }
193}
194
195#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
197pub struct ResourceReference {
198    pub uri: String,
200
201    #[serde(skip_serializing_if = "Option::is_none")]
203    pub text: Option<String>,
204}
205
206#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
208pub struct PromptListChangedNotification {
209    #[serde(flatten)]
211    pub metadata: HashMap<String, Value>,
212}
213
214impl PromptListChangedNotification {
215    pub fn new() -> Self {
217        Self::default()
218    }
219
220    pub fn with_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
222        self.metadata.insert(key.into(), value);
223        self
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230    use serde_json::json;
231
232    #[test]
233    fn test_prompt_creation() {
234        let prompt =
235            Prompt::new("code_review", "Review code for best practices").with_arguments(json!({
236                "type": "object",
237                "properties": {
238                    "language": {"type": "string"},
239                    "code": {"type": "string"}
240                },
241                "required": ["code"]
242            }));
243
244        assert_eq!(prompt.name, "code_review");
245        assert_eq!(prompt.description, "Review code for best practices");
246        assert!(prompt.arguments.is_some());
247    }
248
249    #[test]
250    fn test_list_prompts_request() {
251        let request = ListPromptsRequest { cursor: None };
252        let json = serde_json::to_string(&request).unwrap();
253        let deserialized: ListPromptsRequest = serde_json::from_str(&json).unwrap();
254        assert_eq!(request, deserialized);
255    }
256
257    #[test]
258    fn test_get_prompt_request() {
259        let request = GetPromptRequest {
260            name: "code_review".to_string(),
261            arguments: Some(json!({"language": "rust", "code": "fn main() {}"})),
262        };
263
264        let json = serde_json::to_string(&request).unwrap();
265        let deserialized: GetPromptRequest = serde_json::from_str(&json).unwrap();
266        assert_eq!(request, deserialized);
267    }
268
269    #[test]
270    fn test_prompt_message_creation() {
271        let system_msg = PromptMessage::system("You are a helpful assistant");
272        let user_msg = PromptMessage::user("Hello, how are you?");
273        let assistant_msg = PromptMessage::assistant("I'm doing well, thank you!");
274
275        assert_eq!(system_msg.role, MessageRole::System);
276        assert_eq!(user_msg.role, MessageRole::User);
277        assert_eq!(assistant_msg.role, MessageRole::Assistant);
278    }
279
280    #[test]
281    fn test_prompt_content_text() {
282        let content = PromptContent::text("Hello world");
283        let json = serde_json::to_value(&content).unwrap();
284        assert_eq!(json["type"], "text");
285        assert_eq!(json["text"], "Hello world");
286    }
287
288    #[test]
289    fn test_prompt_content_image() {
290        let content = PromptContent::image("base64data", "image/png");
291        let json = serde_json::to_value(&content).unwrap();
292        assert_eq!(json["type"], "image");
293        assert_eq!(json["data"], "base64data");
294        assert_eq!(json["mimeType"], "image/png");
295    }
296
297    #[test]
298    fn test_prompt_content_resource() {
299        let content = PromptContent::resource_with_text("file:///test.txt", "A test file");
300        let json = serde_json::to_value(&content).unwrap();
301        assert_eq!(json["type"], "resource");
302        assert_eq!(json["resource"]["uri"], "file:///test.txt");
303        assert_eq!(json["resource"]["text"], "A test file");
304    }
305
306    #[test]
307    fn test_message_role_serialization() {
308        let system_role = MessageRole::System;
309        let json = serde_json::to_string(&system_role).unwrap();
310        assert_eq!(json, "\"system\"");
311
312        let user_role = MessageRole::User;
313        let json = serde_json::to_string(&user_role).unwrap();
314        assert_eq!(json, "\"user\"");
315
316        let assistant_role = MessageRole::Assistant;
317        let json = serde_json::to_string(&assistant_role).unwrap();
318        assert_eq!(json, "\"assistant\"");
319    }
320}