1use std::collections::HashMap;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use crate::meta::Cursor;
9
10pub trait HasPromptMetadata {
16 fn name(&self) -> &str;
18
19 fn title(&self) -> Option<&str> { None }
21}
22
23pub trait HasPromptDescription {
25 fn description(&self) -> Option<&str> { None }
26}
27
28pub trait HasPromptArguments {
30 fn arguments(&self) -> Option<&Vec<PromptArgument>> { None }
31}
32
33pub trait HasPromptAnnotations {
35 fn annotations(&self) -> Option<&PromptAnnotations> { None }
36}
37
38pub trait HasPromptMeta {
40 fn prompt_meta(&self) -> Option<&HashMap<String, Value>> { None }
41}
42
43pub trait PromptDefinition:
45 HasPromptMetadata + HasPromptDescription + HasPromptArguments + HasPromptAnnotations + HasPromptMeta + Send +
51 Sync
52{
53 fn display_name(&self) -> &str {
55 self.title().unwrap_or_else(|| self.name())
56 }
57
58 fn to_prompt(&self) -> Prompt {
60 Prompt {
61 name: self.name().to_string(),
62 title: self.title().map(String::from),
63 description: self.description().map(String::from),
64 arguments: self.arguments().cloned(),
65 meta: self.prompt_meta().cloned(),
66 }
67 }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct PromptAnnotations {
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub title: Option<String>,
76 }
78
79impl PromptAnnotations {
80 pub fn new() -> Self {
81 Self {
82 title: None,
83 }
84 }
85
86 pub fn with_title(mut self, title: impl Into<String>) -> Self {
87 self.title = Some(title.into());
88 self
89 }
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94#[serde(rename_all = "camelCase")]
95pub struct Prompt {
96 pub name: String,
98 #[serde(skip_serializing_if = "Option::is_none")]
100 pub title: Option<String>,
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub description: Option<String>,
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub arguments: Option<Vec<PromptArgument>>,
107 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
109 pub meta: Option<HashMap<String, Value>>,
110}
111
112impl Prompt {
113 pub fn new(name: impl Into<String>) -> Self {
114 Self {
115 name: name.into(),
116 title: None,
117 description: None,
118 arguments: None,
119 meta: None,
120 }
121 }
122
123 pub fn with_title(mut self, title: impl Into<String>) -> Self {
124 self.title = Some(title.into());
125 self
126 }
127
128 pub fn with_description(mut self, description: impl Into<String>) -> Self {
129 self.description = Some(description.into());
130 self
131 }
132
133 pub fn with_arguments(mut self, arguments: Vec<PromptArgument>) -> Self {
134 self.arguments = Some(arguments);
135 self
136 }
137
138 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
139 self.meta = Some(meta);
140 self
141 }
142}
143
144impl HasPromptMetadata for Prompt {
148 fn name(&self) -> &str { &self.name }
149 fn title(&self) -> Option<&str> { self.title.as_deref() }
150}
151
152impl HasPromptDescription for Prompt {
153 fn description(&self) -> Option<&str> { self.description.as_deref() }
154}
155
156impl HasPromptArguments for Prompt {
157 fn arguments(&self) -> Option<&Vec<PromptArgument>> { self.arguments.as_ref() }
158}
159
160impl HasPromptAnnotations for Prompt {
161 fn annotations(&self) -> Option<&PromptAnnotations> { None } }
163
164impl HasPromptMeta for Prompt {
165 fn prompt_meta(&self) -> Option<&HashMap<String, Value>> { self.meta.as_ref() }
166}
167
168impl<T> PromptDefinition for T
170where
171 T: HasPromptMetadata + HasPromptDescription + HasPromptArguments + HasPromptAnnotations + HasPromptMeta + Send + Sync
172{}
173
174#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
176#[serde(rename_all = "lowercase")]
177pub enum Role {
178 #[serde(rename = "user")]
179 User,
180 #[serde(rename = "assistant")]
181 Assistant,
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize)]
186#[serde(rename_all = "camelCase")]
187pub struct PromptArgument {
188 pub name: String,
190 #[serde(skip_serializing_if = "Option::is_none")]
192 pub title: Option<String>,
193 #[serde(skip_serializing_if = "Option::is_none")]
195 pub description: Option<String>,
196 #[serde(skip_serializing_if = "Option::is_none")]
198 pub required: Option<bool>,
199}
200
201impl PromptArgument {
202 pub fn new(name: impl Into<String>) -> Self {
203 Self {
204 name: name.into(),
205 title: None,
206 description: None,
207 required: None,
208 }
209 }
210
211 pub fn with_title(mut self, title: impl Into<String>) -> Self {
212 self.title = Some(title.into());
213 self
214 }
215
216 pub fn with_description(mut self, description: impl Into<String>) -> Self {
217 self.description = Some(description.into());
218 self
219 }
220
221 pub fn required(mut self) -> Self {
222 self.required = Some(true);
223 self
224 }
225
226 pub fn optional(mut self) -> Self {
227 self.required = Some(false);
228 self
229 }
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
234#[serde(rename_all = "camelCase")]
235pub struct ListPromptsParams {
236 #[serde(skip_serializing_if = "Option::is_none")]
238 pub cursor: Option<Cursor>,
239 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
241 pub meta: Option<HashMap<String, Value>>,
242}
243
244impl ListPromptsParams {
245 pub fn new() -> Self {
246 Self {
247 cursor: None,
248 meta: None,
249 }
250 }
251
252 pub fn with_cursor(mut self, cursor: Cursor) -> Self {
253 self.cursor = Some(cursor);
254 self
255 }
256
257 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
258 self.meta = Some(meta);
259 self
260 }
261}
262
263impl Default for ListPromptsParams {
264 fn default() -> Self {
265 Self::new()
266 }
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
271#[serde(rename_all = "camelCase")]
272pub struct ListPromptsRequest {
273 pub method: String,
275 pub params: ListPromptsParams,
277}
278
279impl ListPromptsRequest {
280 pub fn new() -> Self {
281 Self {
282 method: "prompts/list".to_string(),
283 params: ListPromptsParams::new(),
284 }
285 }
286
287 pub fn with_cursor(mut self, cursor: Cursor) -> Self {
288 self.params = self.params.with_cursor(cursor);
289 self
290 }
291
292 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
293 self.params = self.params.with_meta(meta);
294 self
295 }
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize)]
300#[serde(rename_all = "camelCase")]
301pub struct ListPromptsResult {
302 pub prompts: Vec<Prompt>,
304 #[serde(skip_serializing_if = "Option::is_none")]
306 pub next_cursor: Option<Cursor>,
307 #[serde(
309 default,
310 skip_serializing_if = "Option::is_none",
311 alias = "_meta",
312 rename = "_meta"
313 )]
314 pub meta: Option<HashMap<String, Value>>,
315}
316
317impl ListPromptsResult {
318 pub fn new(prompts: Vec<Prompt>) -> Self {
319 Self {
320 prompts,
321 next_cursor: None,
322 meta: None,
323 }
324 }
325
326 pub fn with_next_cursor(mut self, cursor: Cursor) -> Self {
327 self.next_cursor = Some(cursor);
328 self
329 }
330
331 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
332 self.meta = Some(meta);
333 self
334 }
335}
336
337#[derive(Debug, Clone, Serialize, Deserialize)]
339#[serde(rename_all = "camelCase")]
340pub struct GetPromptParams {
341 pub name: String,
343 #[serde(skip_serializing_if = "Option::is_none")]
345 pub arguments: Option<HashMap<String, String>>,
346 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
348 pub meta: Option<HashMap<String, Value>>,
349}
350
351impl GetPromptParams {
352 pub fn new(name: impl Into<String>) -> Self {
353 Self {
354 name: name.into(),
355 arguments: None,
356 meta: None,
357 }
358 }
359
360 pub fn with_arguments(mut self, arguments: HashMap<String, String>) -> Self {
361 self.arguments = Some(arguments);
362 self
363 }
364
365 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
366 self.meta = Some(meta);
367 self
368 }
369}
370
371#[derive(Debug, Clone, Serialize, Deserialize)]
373#[serde(rename_all = "camelCase")]
374pub struct GetPromptRequest {
375 pub method: String,
377 pub params: GetPromptParams,
379}
380
381impl GetPromptRequest {
382 pub fn new(name: impl Into<String>) -> Self {
383 Self {
384 method: "prompts/get".to_string(),
385 params: GetPromptParams::new(name),
386 }
387 }
388
389 pub fn with_arguments(mut self, arguments: HashMap<String, String>) -> Self {
390 self.params = self.params.with_arguments(arguments);
391 self
392 }
393
394 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
395 self.params = self.params.with_meta(meta);
396 self
397 }
398}
399
400#[derive(Debug, Clone, Serialize, Deserialize)]
402#[serde(rename_all = "camelCase")]
403pub struct PromptMessage {
404 pub role: Role,
406 pub content: ContentBlock,
408}
409
410#[derive(Debug, Clone, Serialize, Deserialize)]
412#[serde(tag = "type", rename_all = "snake_case")]
413pub enum ContentBlock {
414 Text {
416 text: String,
417 },
418 Image {
420 data: String,
421 #[serde(rename = "mimeType")]
422 mime_type: String,
423 },
424 ResourceLink {
426 #[serde(flatten)]
427 resource: ResourceReference,
428 },
429 Resource {
431 resource: ResourceContents,
432 #[serde(skip_serializing_if = "Option::is_none")]
434 annotations: Option<Value>, #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
437 meta: Option<HashMap<String, Value>>,
438 },
439}
440
441#[derive(Debug, Clone, Serialize, Deserialize)]
443#[serde(rename_all = "camelCase")]
444pub struct ResourceReference {
445 pub uri: String,
446 pub name: String,
447 #[serde(skip_serializing_if = "Option::is_none")]
448 pub title: Option<String>,
449 #[serde(skip_serializing_if = "Option::is_none")]
450 pub description: Option<String>,
451 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
452 pub mime_type: Option<String>,
453}
454
455#[derive(Debug, Clone, Serialize, Deserialize)]
457#[serde(tag = "type", rename_all = "snake_case")]
458pub enum ResourceContents {
459 Text {
461 uri: String,
462 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
463 mime_type: Option<String>,
464 text: String,
465 },
466 Blob {
468 uri: String,
469 #[serde(rename = "mimeType")]
470 mime_type: String,
471 blob: String, },
473}
474
475impl PromptMessage {
476 pub fn user_text(content: impl Into<String>) -> Self {
477 Self {
478 role: Role::User,
479 content: ContentBlock::Text {
480 text: content.into(),
481 },
482 }
483 }
484
485 pub fn assistant_text(content: impl Into<String>) -> Self {
486 Self {
487 role: Role::Assistant,
488 content: ContentBlock::Text {
489 text: content.into(),
490 },
491 }
492 }
493
494 pub fn user_image(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
495 Self {
496 role: Role::User,
497 content: ContentBlock::Image {
498 data: data.into(),
499 mime_type: mime_type.into(),
500 },
501 }
502 }
503
504 pub fn text(content: impl Into<String>) -> Self {
505 Self::user_text(content)
507 }
508}
509
510#[derive(Debug, Clone, Serialize, Deserialize)]
512#[serde(rename_all = "camelCase")]
513pub struct GetPromptResult {
514 #[serde(skip_serializing_if = "Option::is_none")]
516 pub description: Option<String>,
517 pub messages: Vec<PromptMessage>,
519 #[serde(
521 default,
522 skip_serializing_if = "Option::is_none",
523 alias = "_meta",
524 rename = "_meta"
525 )]
526 pub meta: Option<HashMap<String, Value>>,
527}
528
529impl GetPromptResult {
530 pub fn new(messages: Vec<PromptMessage>) -> Self {
531 Self {
532 description: None,
533 messages,
534 meta: None,
535 }
536 }
537
538 pub fn with_description(mut self, description: impl Into<String>) -> Self {
539 self.description = Some(description.into());
540 self
541 }
542
543 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
544 self.meta = Some(meta);
545 self
546 }
547}
548
549
550use crate::traits::*;
553
554impl Params for ListPromptsParams {}
556
557impl HasListPromptsParams for ListPromptsParams {
558 fn cursor(&self) -> Option<&Cursor> {
559 self.cursor.as_ref()
560 }
561}
562
563impl HasMetaParam for ListPromptsParams {
564 fn meta(&self) -> Option<&HashMap<String, Value>> {
565 self.meta.as_ref()
566 }
567}
568
569impl HasMethod for ListPromptsRequest {
571 fn method(&self) -> &str {
572 &self.method
573 }
574}
575
576impl HasParams for ListPromptsRequest {
577 fn params(&self) -> Option<&dyn Params> {
578 Some(&self.params)
579 }
580}
581
582impl HasData for ListPromptsResult {
584 fn data(&self) -> HashMap<String, Value> {
585 let mut data = HashMap::new();
586 data.insert("prompts".to_string(), serde_json::to_value(&self.prompts).unwrap_or(Value::Null));
587 if let Some(ref next_cursor) = self.next_cursor {
588 data.insert("nextCursor".to_string(), Value::String(next_cursor.as_str().to_string()));
589 }
590 data
591 }
592}
593
594impl HasMeta for ListPromptsResult {
595 fn meta(&self) -> Option<HashMap<String, Value>> {
596 self.meta.clone()
597 }
598}
599
600impl RpcResult for ListPromptsResult {}
601
602impl crate::traits::ListPromptsResult for ListPromptsResult {
603 fn prompts(&self) -> &Vec<Prompt> {
604 &self.prompts
605 }
606
607 fn next_cursor(&self) -> Option<&Cursor> {
608 self.next_cursor.as_ref()
609 }
610}
611
612impl Params for GetPromptParams {}
614
615impl HasGetPromptParams for GetPromptParams {
616 fn name(&self) -> &String {
617 &self.name
618 }
619
620 fn arguments(&self) -> Option<&HashMap<String, String>> {
621 self.arguments.as_ref()
622 }
623}
624
625impl HasMetaParam for GetPromptParams {
626 fn meta(&self) -> Option<&HashMap<String, Value>> {
627 self.meta.as_ref()
628 }
629}
630
631impl HasMethod for GetPromptRequest {
633 fn method(&self) -> &str {
634 &self.method
635 }
636}
637
638impl HasParams for GetPromptRequest {
639 fn params(&self) -> Option<&dyn Params> {
640 Some(&self.params)
641 }
642}
643
644impl HasData for GetPromptResult {
646 fn data(&self) -> HashMap<String, Value> {
647 let mut data = HashMap::new();
648 data.insert("messages".to_string(), serde_json::to_value(&self.messages).unwrap_or(Value::Null));
649 if let Some(ref description) = self.description {
650 data.insert("description".to_string(), Value::String(description.clone()));
651 }
652 data
653 }
654}
655
656impl HasMeta for GetPromptResult {
657 fn meta(&self) -> Option<HashMap<String, Value>> {
658 self.meta.clone()
659 }
660}
661
662impl RpcResult for GetPromptResult {}
663
664impl crate::traits::GetPromptResult for GetPromptResult {
665 fn description(&self) -> Option<&String> {
666 self.description.as_ref()
667 }
668
669 fn messages(&self) -> &Vec<PromptMessage> {
670 &self.messages
671 }
672}
673
674#[cfg(test)]
675mod tests {
676 use super::*;
677
678 #[test]
679 fn test_prompt_creation() {
680 let arg = PromptArgument::new("topic")
681 .with_description("The topic to write about")
682 .required();
683
684 let prompt = Prompt::new("write_essay")
685 .with_description("Write an essay about a topic")
686 .with_arguments(vec![arg]);
687
688 assert_eq!(prompt.name, "write_essay");
689 assert!(prompt.description.is_some());
690 assert!(prompt.arguments.is_some());
691 }
692
693 #[test]
694 fn test_prompt_message() {
695 let text_msg = PromptMessage::text("Hello, world!");
696 let user_image_msg = PromptMessage::user_image("base64data", "image/png");
697 let assistant_text_msg = PromptMessage::assistant_text("Response text");
698
699 assert_eq!(text_msg.role, Role::User);
701 assert!(matches!(text_msg.content, ContentBlock::Text { .. }));
702
703 assert_eq!(user_image_msg.role, Role::User);
704 assert!(matches!(user_image_msg.content, ContentBlock::Image { .. }));
705
706 assert_eq!(assistant_text_msg.role, Role::Assistant);
707 assert!(matches!(assistant_text_msg.content, ContentBlock::Text { .. }));
708 }
709
710 #[test]
711 fn test_get_prompt_request() {
712 let mut args = HashMap::new();
713 args.insert("topic".to_string(), "AI Safety".to_string()); let request = GetPromptRequest::new("write_essay")
716 .with_arguments(args);
717
718 assert_eq!(request.params.name, "write_essay");
719 assert!(request.params.arguments.is_some());
720
721 if let Some(ref arguments) = request.params.arguments {
723 assert_eq!(arguments.get("topic"), Some(&"AI Safety".to_string()));
724 }
725 }
726
727 #[test]
728 fn test_get_prompt_response() {
729 let messages = vec![
730 PromptMessage::user_text("Write an essay about: "),
731 PromptMessage::assistant_text("AI Safety"),
732 ];
733
734 let response = GetPromptResult::new(messages)
735 .with_description("Generated essay prompt");
736
737 assert_eq!(response.messages.len(), 2);
738 assert!(response.description.is_some());
739
740 assert_eq!(response.messages[0].role, Role::User);
742 assert_eq!(response.messages[1].role, Role::Assistant);
743 }
744
745 #[test]
746 fn test_serialization() {
747 let prompt = Prompt::new("test_prompt")
748 .with_description("A test prompt");
749
750 let json = serde_json::to_string(&prompt).unwrap();
751 assert!(json.contains("test_prompt"));
752 assert!(json.contains("A test prompt"));
753
754 let parsed: Prompt = serde_json::from_str(&json).unwrap();
755 assert_eq!(parsed.name, "test_prompt");
756 }
757}