1use crate::content::{Annotations, EmbeddedResource, ImageContent};
2use crate::handler::PromptError;
3use crate::resource::ResourceContents;
4use base64::engine::{general_purpose::STANDARD as BASE64_STANDARD, Engine};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct Prompt {
11 pub name: String,
13 #[serde(skip_serializing_if = "Option::is_none")]
15 pub description: Option<String>,
16 #[serde(skip_serializing_if = "Option::is_none")]
18 pub arguments: Option<Vec<PromptArgument>>,
19}
20
21impl Prompt {
22 pub fn new<N, D>(
24 name: N,
25 description: Option<D>,
26 arguments: Option<Vec<PromptArgument>>,
27 ) -> Self
28 where
29 N: Into<String>,
30 D: Into<String>,
31 {
32 Prompt {
33 name: name.into(),
34 description: description.map(Into::into),
35 arguments,
36 }
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
42pub struct PromptArgument {
43 pub name: String,
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub description: Option<String>,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub required: Option<bool>,
51}
52
53#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
55#[serde(rename_all = "lowercase")]
56pub enum PromptMessageRole {
57 User,
58 Assistant,
59}
60
61#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
63#[serde(tag = "type", rename_all = "lowercase")]
64pub enum PromptMessageContent {
65 Text { text: String },
67 Image { image: ImageContent },
69 Resource { resource: EmbeddedResource },
71}
72
73#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
75pub struct PromptMessage {
76 pub role: PromptMessageRole,
78 pub content: PromptMessageContent,
80}
81
82impl PromptMessage {
83 pub fn new_text<S: Into<String>>(role: PromptMessageRole, text: S) -> Self {
85 Self {
86 role,
87 content: PromptMessageContent::Text { text: text.into() },
88 }
89 }
90
91 pub fn new_image<S: Into<String>>(
92 role: PromptMessageRole,
93 data: S,
94 mime_type: S,
95 annotations: Option<Annotations>,
96 ) -> Result<Self, PromptError> {
97 let data = data.into();
98 let mime_type = mime_type.into();
99
100 BASE64_STANDARD.decode(&data).map_err(|_| {
102 PromptError::InvalidParameters("Image data must be valid base64".to_string())
103 })?;
104
105 if !mime_type.starts_with("image/") {
107 return Err(PromptError::InvalidParameters(
108 "MIME type must be a valid image type (e.g. image/jpeg)".to_string(),
109 ));
110 }
111
112 Ok(Self {
113 role,
114 content: PromptMessageContent::Image {
115 image: ImageContent {
116 data,
117 mime_type,
118 annotations,
119 },
120 },
121 })
122 }
123
124 pub fn new_resource(
126 role: PromptMessageRole,
127 uri: String,
128 mime_type: String,
129 text: Option<String>,
130 annotations: Option<Annotations>,
131 ) -> Self {
132 let resource_contents = ResourceContents::TextResourceContents {
133 uri,
134 mime_type: Some(mime_type),
135 text: text.unwrap_or_default(),
136 };
137
138 Self {
139 role,
140 content: PromptMessageContent::Resource {
141 resource: EmbeddedResource {
142 resource: resource_contents,
143 annotations,
144 },
145 },
146 }
147 }
148}
149
150#[derive(Debug, Serialize, Deserialize)]
152pub struct PromptTemplate {
153 pub id: String,
154 pub template: String,
155 pub arguments: Vec<PromptArgumentTemplate>,
156}
157
158#[derive(Debug, Serialize, Deserialize)]
160pub struct PromptArgumentTemplate {
161 pub name: String,
162 pub description: Option<String>,
163 pub required: Option<bool>,
164}