feat: add resource_link support to tools and prompts (#381)
* feat: add resource_link support to tools and prompts * chore: remove unused serde_json import from test_resource_link_integration.rs * chore: remove unused serde_json import from test_resource_link.rs
This commit is contained in:
parent
d328157be3
commit
98b77fd94a
2 changed files with 2129 additions and 7 deletions
|
|
@ -51,13 +51,14 @@ pub struct RawAudioContent {
|
|||
pub type AudioContent = Annotated<RawAudioContent>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum RawContent {
|
||||
Text(RawTextContent),
|
||||
Image(RawImageContent),
|
||||
Resource(RawEmbeddedResource),
|
||||
Audio(AudioContent),
|
||||
ResourceLink(super::resource::RawResource),
|
||||
}
|
||||
|
||||
pub type Content = Annotated<RawContent>;
|
||||
|
|
@ -123,6 +124,19 @@ impl RawContent {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the resource link if this is a ResourceLink variant
|
||||
pub fn as_resource_link(&self) -> Option<&super::resource::RawResource> {
|
||||
match self {
|
||||
RawContent::ResourceLink(link) => Some(link),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a resource link content
|
||||
pub fn resource_link(resource: super::resource::RawResource) -> Self {
|
||||
RawContent::ResourceLink(resource)
|
||||
}
|
||||
}
|
||||
|
||||
impl Content {
|
||||
|
|
@ -145,6 +159,11 @@ impl Content {
|
|||
pub fn json<S: Serialize>(json: S) -> Result<Self, crate::ErrorData> {
|
||||
RawContent::json(json).map(|c| c.no_annotation())
|
||||
}
|
||||
|
||||
/// Create a resource link content
|
||||
pub fn resource_link(resource: super::resource::RawResource) -> Self {
|
||||
RawContent::resource_link(resource).no_annotation()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
|
|
@ -207,4 +226,47 @@ mod tests {
|
|||
assert!(json.contains("mimeType"));
|
||||
assert!(!json.contains("mime_type"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resource_link_serialization() {
|
||||
use super::super::resource::RawResource;
|
||||
|
||||
let resource_link = RawContent::ResourceLink(RawResource {
|
||||
uri: "file:///test.txt".to_string(),
|
||||
name: "test.txt".to_string(),
|
||||
description: Some("A test file".to_string()),
|
||||
mime_type: Some("text/plain".to_string()),
|
||||
size: Some(100),
|
||||
});
|
||||
|
||||
let json = serde_json::to_string(&resource_link).unwrap();
|
||||
println!("ResourceLink JSON: {}", json);
|
||||
|
||||
// Verify it contains the correct type tag
|
||||
assert!(json.contains("\"type\":\"resource_link\""));
|
||||
assert!(json.contains("\"uri\":\"file:///test.txt\""));
|
||||
assert!(json.contains("\"name\":\"test.txt\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resource_link_deserialization() {
|
||||
let json = r#"{
|
||||
"type": "resource_link",
|
||||
"uri": "file:///example.txt",
|
||||
"name": "example.txt",
|
||||
"description": "Example file",
|
||||
"mimeType": "text/plain"
|
||||
}"#;
|
||||
|
||||
let content: RawContent = serde_json::from_str(json).unwrap();
|
||||
|
||||
if let RawContent::ResourceLink(resource) = content {
|
||||
assert_eq!(resource.uri, "file:///example.txt");
|
||||
assert_eq!(resource.name, "example.txt");
|
||||
assert_eq!(resource.description, Some("Example file".to_string()));
|
||||
assert_eq!(resource.mime_type, Some("text/plain".to_string()));
|
||||
} else {
|
||||
panic!("Expected ResourceLink variant");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ pub enum PromptMessageRole {
|
|||
|
||||
/// Content types that can be included in prompt messages
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum PromptMessageContent {
|
||||
/// Plain text content
|
||||
|
|
@ -78,12 +78,22 @@ pub enum PromptMessageContent {
|
|||
},
|
||||
/// Embedded server-side resource
|
||||
Resource { resource: EmbeddedResource },
|
||||
/// A link to a resource that can be fetched separately
|
||||
ResourceLink {
|
||||
#[serde(flatten)]
|
||||
link: super::resource::Resource,
|
||||
},
|
||||
}
|
||||
|
||||
impl PromptMessageContent {
|
||||
pub fn text(text: impl Into<String>) -> Self {
|
||||
Self::Text { text: text.into() }
|
||||
}
|
||||
|
||||
/// Create a resource link content
|
||||
pub fn resource_link(resource: super::resource::Resource) -> Self {
|
||||
Self::ResourceLink { link: resource }
|
||||
}
|
||||
}
|
||||
|
||||
/// A message in a prompt conversation
|
||||
|
|
@ -151,6 +161,14 @@ impl PromptMessage {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new resource link message
|
||||
pub fn new_resource_link(role: PromptMessageRole, resource: super::resource::Resource) -> Self {
|
||||
Self {
|
||||
role,
|
||||
content: PromptMessageContent::ResourceLink { link: resource },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -173,4 +191,43 @@ mod tests {
|
|||
assert!(json.contains("mimeType"));
|
||||
assert!(!json.contains("mime_type"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prompt_message_resource_link_serialization() {
|
||||
use super::super::resource::RawResource;
|
||||
|
||||
let resource = RawResource::new("file:///test.txt", "test.txt");
|
||||
let message =
|
||||
PromptMessage::new_resource_link(PromptMessageRole::User, resource.no_annotation());
|
||||
|
||||
let json = serde_json::to_string(&message).unwrap();
|
||||
println!("PromptMessage with ResourceLink JSON: {}", json);
|
||||
|
||||
// Verify it contains the correct type tag
|
||||
assert!(json.contains("\"type\":\"resource_link\""));
|
||||
assert!(json.contains("\"uri\":\"file:///test.txt\""));
|
||||
assert!(json.contains("\"name\":\"test.txt\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prompt_message_content_resource_link_deserialization() {
|
||||
let json = r#"{
|
||||
"type": "resource_link",
|
||||
"uri": "file:///example.txt",
|
||||
"name": "example.txt",
|
||||
"description": "Example file",
|
||||
"mimeType": "text/plain"
|
||||
}"#;
|
||||
|
||||
let content: PromptMessageContent = serde_json::from_str(json).unwrap();
|
||||
|
||||
if let PromptMessageContent::ResourceLink { link } = content {
|
||||
assert_eq!(link.uri, "file:///example.txt");
|
||||
assert_eq!(link.name, "example.txt");
|
||||
assert_eq!(link.description, Some("Example file".to_string()));
|
||||
assert_eq!(link.mime_type, Some("text/plain".to_string()));
|
||||
} else {
|
||||
panic!("Expected ResourceLink variant");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue