use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Resource {
pub uri: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<ResourceAnnotations>,
}
impl Resource {
pub fn new(uri: impl Into<String>, name: impl Into<String>) -> Self {
Self {
uri: uri.into(),
name: name.into(),
title: None,
description: None,
mime_type: None,
size: None,
annotations: None,
}
}
pub fn builder(uri: impl Into<String>, name: impl Into<String>) -> ResourceBuilder {
ResourceBuilder::new(uri, name)
}
}
#[derive(Debug)]
pub struct ResourceBuilder {
resource: Resource,
}
impl ResourceBuilder {
pub fn new(uri: impl Into<String>, name: impl Into<String>) -> Self {
Self {
resource: Resource::new(uri, name),
}
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.resource.title = Some(title.into());
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.resource.description = Some(description.into());
self
}
pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
self.resource.mime_type = Some(mime_type.into());
self
}
pub fn size(mut self, size: u64) -> Self {
self.resource.size = Some(size);
self
}
pub fn annotations(mut self, annotations: ResourceAnnotations) -> Self {
self.resource.annotations = Some(annotations);
self
}
pub fn build(self) -> Resource {
self.resource
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct ResourceAnnotations {
#[serde(skip_serializing_if = "Option::is_none")]
pub audience: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub priority: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_modified: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ResourceContents {
pub uri: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub blob: Option<String>,
}
impl ResourceContents {
pub fn text(uri: impl Into<String>, content: impl Into<String>) -> Self {
Self {
uri: uri.into(),
mime_type: Some("text/plain".to_string()),
text: Some(content.into()),
blob: None,
}
}
pub fn blob(
uri: impl Into<String>,
data: impl Into<String>,
mime_type: impl Into<String>,
) -> Self {
Self {
uri: uri.into(),
mime_type: Some(mime_type.into()),
text: None,
blob: Some(data.into()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ResourceTemplate {
pub uri_template: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
}
impl ResourceTemplate {
pub fn new(uri_template: impl Into<String>, name: impl Into<String>) -> Self {
Self {
uri_template: uri_template.into(),
name: name.into(),
title: None,
description: None,
mime_type: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Prompt {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<Vec<PromptArgument>>,
}
impl Prompt {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
title: None,
description: None,
arguments: None,
}
}
pub fn builder(name: impl Into<String>) -> PromptBuilder {
PromptBuilder::new(name)
}
}
#[derive(Debug)]
pub struct PromptBuilder {
prompt: Prompt,
}
impl PromptBuilder {
pub fn new(name: impl Into<String>) -> Self {
Self {
prompt: Prompt::new(name),
}
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.prompt.title = Some(title.into());
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.prompt.description = Some(description.into());
self
}
pub fn argument(mut self, arg: PromptArgument) -> Self {
self.prompt.arguments.get_or_insert_with(Vec::new).push(arg);
self
}
pub fn build(self) -> Prompt {
self.prompt
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PromptArgument {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
}
impl PromptArgument {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
description: None,
required: None,
}
}
pub fn required(name: impl Into<String>, description: impl Into<String>) -> Self {
Self {
name: name.into(),
description: Some(description.into()),
required: Some(true),
}
}
pub fn optional(name: impl Into<String>, description: impl Into<String>) -> Self {
Self {
name: name.into(),
description: Some(description.into()),
required: Some(false),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PromptMessage {
pub role: String,
pub content: PromptContent,
}
impl PromptMessage {
pub fn user_text(text: impl Into<String>) -> Self {
Self {
role: "user".to_string(),
content: PromptContent::Text {
r#type: "text".to_string(),
text: text.into(),
},
}
}
pub fn assistant_text(text: impl Into<String>) -> Self {
Self {
role: "assistant".to_string(),
content: PromptContent::Text {
r#type: "text".to_string(),
text: text.into(),
},
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum PromptContent {
Text { r#type: String, text: String },
Image {
r#type: String,
data: String,
mime_type: String,
},
Audio {
r#type: String,
data: String,
mime_type: String,
},
Resource {
r#type: String,
resource: EmbeddedResource,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct EmbeddedResource {
pub uri: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub blob: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GetPromptResult {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub messages: Vec<PromptMessage>,
}
impl GetPromptResult {
pub fn new(messages: Vec<PromptMessage>) -> Self {
Self {
description: None,
messages,
}
}
pub fn with_description(description: impl Into<String>, messages: Vec<PromptMessage>) -> Self {
Self {
description: Some(description.into()),
messages,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ResourceListResult {
pub resources: Vec<Resource>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PromptListResult {
pub prompts: Vec<Prompt>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ResourceTemplateListResult {
pub resource_templates: Vec<ResourceTemplate>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resource_creation() {
let resource = Resource::new("file:///path/to/file.txt", "file.txt");
assert_eq!(resource.uri, "file:///path/to/file.txt");
assert_eq!(resource.name, "file.txt");
}
#[test]
fn test_resource_builder() {
let resource = Resource::builder("file:///test.md", "test.md")
.title("Test File")
.description("A test markdown file")
.mime_type("text/markdown")
.size(1024)
.build();
assert_eq!(resource.title, Some("Test File".to_string()));
assert_eq!(resource.size, Some(1024));
}
#[test]
fn test_resource_contents_text() {
let contents = ResourceContents::text("file:///test.txt", "Hello, World!");
assert!(contents.text.is_some());
assert!(contents.blob.is_none());
}
#[test]
fn test_resource_contents_blob() {
let contents = ResourceContents::blob("file:///image.png", "base64data", "image/png");
assert!(contents.blob.is_some());
assert!(contents.text.is_none());
}
#[test]
fn test_prompt_creation() {
let prompt = Prompt::new("code_review");
assert_eq!(prompt.name, "code_review");
}
#[test]
fn test_prompt_builder() {
let prompt = Prompt::builder("code_review")
.title("Code Review")
.description("Review code for best practices")
.argument(PromptArgument::required("code", "Code to review"))
.argument(PromptArgument::optional("language", "Programming language"))
.build();
assert_eq!(prompt.title, Some("Code Review".to_string()));
assert_eq!(prompt.arguments.as_ref().unwrap().len(), 2);
}
#[test]
fn test_prompt_message() {
let user_msg = PromptMessage::user_text("Please review this code");
assert_eq!(user_msg.role, "user");
let asst_msg = PromptMessage::assistant_text("I'll review the code");
assert_eq!(asst_msg.role, "assistant");
}
#[test]
fn test_get_prompt_result() {
let result = GetPromptResult::with_description(
"Code review prompt",
vec![PromptMessage::user_text("Review this")],
);
assert_eq!(result.description, Some("Code review prompt".to_string()));
assert_eq!(result.messages.len(), 1);
}
#[test]
fn test_resource_serialization() {
let resource = Resource::builder("file:///test.txt", "test.txt")
.mime_type("text/plain")
.build();
let json = serde_json::to_string(&resource).unwrap();
let parsed: Resource = serde_json::from_str(&json).unwrap();
assert_eq!(resource, parsed);
}
#[test]
fn test_prompt_serialization() {
let prompt = Prompt::builder("test")
.description("Test prompt")
.argument(PromptArgument::required("input", "Input text"))
.build();
let json = serde_json::to_string(&prompt).unwrap();
let parsed: Prompt = serde_json::from_str(&json).unwrap();
assert_eq!(prompt, parsed);
}
}