use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Prompt {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default)]
pub arguments: Vec<PromptArgument>,
#[serde(default)]
pub mind_context: bool,
#[serde(default)]
pub inject_project_context: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptArgument {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default)]
pub required: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum PromptContent {
#[serde(rename = "text")]
Text { text: String },
#[serde(rename = "image")]
Image { data: String, mime_type: String },
#[serde(rename = "mind_facts")]
MindFacts { query: String, limit: usize },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptMessage {
pub role: String,
pub content: PromptContent,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListPromptsParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListPromptsResult {
pub prompts: Vec<Prompt>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetPromptParams {
pub name: String,
#[serde(default)]
pub arguments: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetPromptResult {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub messages: Vec<PromptMessage>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_prompt_roundtrip() {
let prompt = Prompt {
name: "engagement_summary".to_string(),
description: Some("Summarize engagement status".to_string()),
arguments: vec![PromptArgument {
name: "client_name".to_string(),
description: Some("Client to summarize".to_string()),
required: true,
}],
mind_context: true,
inject_project_context: false,
};
let json = serde_json::to_string(&prompt).unwrap();
let parsed: Prompt = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.name, "engagement_summary");
assert!(parsed.mind_context);
assert!(!parsed.inject_project_context);
assert_eq!(parsed.arguments.len(), 1);
assert!(parsed.arguments[0].required);
}
#[test]
fn test_prompt_minimal() {
let json = r#"{"name":"hello"}"#;
let parsed: Prompt = serde_json::from_str(json).unwrap();
assert_eq!(parsed.name, "hello");
assert!(parsed.arguments.is_empty());
assert!(!parsed.mind_context);
}
#[test]
fn test_prompt_content_text() {
let content = PromptContent::Text {
text: "Summarize the Recro engagement.".to_string(),
};
let json = serde_json::to_string(&content).unwrap();
let parsed: PromptContent = serde_json::from_str(&json).unwrap();
match parsed {
PromptContent::Text { text } => {
assert_eq!(text, "Summarize the Recro engagement.");
}
_ => panic!("expected Text"),
}
}
#[test]
fn test_prompt_content_image() {
let content = PromptContent::Image {
data: "iVBORw0KGgo=".to_string(),
mime_type: "image/png".to_string(),
};
let json = serde_json::to_string(&content).unwrap();
let parsed: PromptContent = serde_json::from_str(&json).unwrap();
match parsed {
PromptContent::Image { data, mime_type } => {
assert_eq!(mime_type, "image/png");
assert!(!data.is_empty());
}
_ => panic!("expected Image"),
}
}
#[test]
fn test_prompt_content_mind_facts() {
let content = PromptContent::MindFacts {
query: "engagement status".to_string(),
limit: 5,
};
let json = serde_json::to_string(&content).unwrap();
let parsed: PromptContent = serde_json::from_str(&json).unwrap();
match parsed {
PromptContent::MindFacts { query, limit } => {
assert_eq!(query, "engagement status");
assert_eq!(limit, 5);
}
_ => panic!("expected MindFacts"),
}
}
#[test]
fn test_prompt_message_roundtrip() {
let msg = PromptMessage {
role: "user".to_string(),
content: PromptContent::Text {
text: "Review this code.".to_string(),
},
};
let json = serde_json::to_string(&msg).unwrap();
let parsed: PromptMessage = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.role, "user");
}
#[test]
fn test_get_prompt_result() {
let result = GetPromptResult {
description: Some("Code review prompt".to_string()),
messages: vec![
PromptMessage {
role: "user".to_string(),
content: PromptContent::Text {
text: "Review the following code:".to_string(),
},
},
PromptMessage {
role: "user".to_string(),
content: PromptContent::MindFacts {
query: "code review patterns".to_string(),
limit: 3,
},
},
],
};
let json = serde_json::to_string(&result).unwrap();
let parsed: GetPromptResult = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.messages.len(), 2);
}
#[test]
fn test_list_prompts_result_pagination() {
let result = ListPromptsResult {
prompts: vec![Prompt {
name: "review".to_string(),
description: None,
arguments: vec![],
mind_context: false,
inject_project_context: false,
}],
next_cursor: Some("page2".to_string()),
};
let json = serde_json::to_string(&result).unwrap();
let parsed: ListPromptsResult = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.prompts.len(), 1);
assert_eq!(parsed.next_cursor.as_deref(), Some("page2"));
}
#[test]
fn test_get_prompt_params() {
let params = GetPromptParams {
name: "engagement_summary".to_string(),
arguments: [("client_name".to_string(), "Recro".to_string())]
.into_iter()
.collect(),
};
let json = serde_json::to_string(¶ms).unwrap();
let parsed: GetPromptParams = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.name, "engagement_summary");
assert_eq!(parsed.arguments.get("client_name").unwrap(), "Recro");
}
}