Skip to main content

systemprompt_models/artifacts/list/
mod.rs

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