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}