systemprompt_models/artifacts/list/
mod.rs

1use crate::artifacts::metadata::ExecutionMetadata;
2use crate::artifacts::traits::Artifact;
3use crate::artifacts::types::ArtifactType;
4use crate::execution::context::RequestContext;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use serde_json::{json, Value as JsonValue};
8use systemprompt_identifiers::SkillId;
9
10fn default_artifact_type() -> String {
11    "list".to_string()
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
15pub struct ListItem {
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub id: Option<String>,
18    pub title: String,
19    pub summary: String,
20    pub link: String,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub uri: Option<String>,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub slug: Option<String>,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub source_id: Option<String>,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub category: Option<String>,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub description: Option<String>,
31}
32
33impl ListItem {
34    pub fn new(
35        title: impl Into<String>,
36        summary: impl Into<String>,
37        link: impl Into<String>,
38    ) -> Self {
39        Self {
40            id: None,
41            title: title.into(),
42            summary: summary.into(),
43            link: link.into(),
44            uri: None,
45            slug: None,
46            source_id: None,
47            category: None,
48            description: None,
49        }
50    }
51
52    pub fn with_id(mut self, id: impl Into<String>) -> Self {
53        self.id = Some(id.into());
54        self
55    }
56
57    pub fn with_uri(mut self, uri: impl Into<String>) -> Self {
58        self.uri = Some(uri.into());
59        self
60    }
61
62    pub fn with_slug(mut self, slug: impl Into<String>) -> Self {
63        self.slug = Some(slug.into());
64        self
65    }
66
67    pub fn with_source_id(mut self, source_id: impl Into<String>) -> Self {
68        self.source_id = Some(source_id.into());
69        self
70    }
71
72    pub fn with_category(mut self, category: impl Into<String>) -> Self {
73        self.category = Some(category.into());
74        self
75    }
76
77    pub fn with_description(mut self, description: impl Into<String>) -> Self {
78        self.description = Some(description.into());
79        self
80    }
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
84pub struct ListArtifact {
85    #[serde(rename = "x-artifact-type")]
86    #[serde(default = "default_artifact_type")]
87    pub artifact_type: String,
88    pub items: Vec<ListItem>,
89    pub count: usize,
90    #[serde(skip)]
91    #[schemars(skip)]
92    metadata: ExecutionMetadata,
93}
94
95impl ListArtifact {
96    pub fn new(ctx: &RequestContext) -> Self {
97        Self {
98            artifact_type: "list".to_string(),
99            items: Vec::new(),
100            count: 0,
101            metadata: ExecutionMetadata::with_request(ctx),
102        }
103    }
104
105    pub fn with_items(mut self, items: Vec<ListItem>) -> Self {
106        self.count = items.len();
107        self.items = items;
108        self
109    }
110
111    pub fn with_execution_id(mut self, id: impl Into<String>) -> Self {
112        self.metadata.execution_id = Some(id.into());
113        self
114    }
115
116    pub fn with_skill(
117        mut self,
118        skill_id: impl Into<SkillId>,
119        skill_name: impl Into<String>,
120    ) -> Self {
121        self.metadata.skill_id = Some(skill_id.into());
122        self.metadata.skill_name = Some(skill_name.into());
123        self
124    }
125}
126
127impl Artifact for ListArtifact {
128    fn artifact_type(&self) -> ArtifactType {
129        ArtifactType::List
130    }
131
132    fn to_schema(&self) -> JsonValue {
133        json!({
134            "type": "object",
135            "properties": {
136                "items": {
137                    "type": "array",
138                    "description": "Array of list items",
139                    "items": {
140                        "type": "object",
141                        "properties": {
142                            "title": {
143                                "type": "string",
144                                "description": "Item title"
145                            },
146                            "summary": {
147                                "type": "string",
148                                "description": "Item summary"
149                            },
150                            "link": {
151                                "type": "string",
152                                "description": "Item URL (full HTTPS URL compatible with resource_loading tool's uris parameter)"
153                            },
154                            "uri": {
155                                "type": "string",
156                                "description": "Standardized URI format (tyingshoelaces://blog/slug) for use with resource_loading tool"
157                            },
158                            "slug": {
159                                "type": "string",
160                                "description": "Content slug - can be used directly with resource_loading tool as tyingshoelaces://blog/{slug}"
161                            }
162                        },
163                        "required": ["title", "summary", "link"]
164                    }
165                },
166                "count": {
167                    "type": "integer",
168                    "description": "Total number of items"
169                },
170                "_execution_id": {
171                    "type": "string",
172                    "description": "Execution ID for tracking"
173                }
174            },
175            "required": ["items"],
176            "x-artifact-type": "list"
177        })
178    }
179}