systemprompt_models/artifacts/list/
mod.rs1use 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}