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::{Value as JsonValue, json};
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 const ARTIFACT_TYPE_STR: &'static str = "list";
97
98 pub fn new(ctx: &RequestContext) -> Self {
99 Self {
100 artifact_type: "list".to_string(),
101 items: Vec::new(),
102 count: 0,
103 metadata: ExecutionMetadata::with_request(ctx),
104 }
105 }
106
107 pub fn with_items(mut self, items: Vec<ListItem>) -> Self {
108 self.count = items.len();
109 self.items = items;
110 self
111 }
112
113 pub fn with_execution_id(mut self, id: impl Into<String>) -> Self {
114 self.metadata.execution_id = Some(id.into());
115 self
116 }
117
118 pub fn with_skill(
119 mut self,
120 skill_id: impl Into<SkillId>,
121 skill_name: impl Into<String>,
122 ) -> Self {
123 self.metadata.skill_id = Some(skill_id.into());
124 self.metadata.skill_name = Some(skill_name.into());
125 self
126 }
127}
128
129impl Artifact for ListArtifact {
130 fn artifact_type(&self) -> ArtifactType {
131 ArtifactType::List
132 }
133
134 fn to_schema(&self) -> JsonValue {
135 json!({
136 "type": "object",
137 "properties": {
138 "items": {
139 "type": "array",
140 "description": "Array of list items",
141 "items": {
142 "type": "object",
143 "properties": {
144 "title": {
145 "type": "string",
146 "description": "Item title"
147 },
148 "summary": {
149 "type": "string",
150 "description": "Item summary"
151 },
152 "link": {
153 "type": "string",
154 "description": "Item URL (full HTTPS URL compatible with resource_loading tool's uris parameter)"
155 },
156 "uri": {
157 "type": "string",
158 "description": "Standardized URI format (tyingshoelaces://blog/slug) for use with resource_loading tool"
159 },
160 "slug": {
161 "type": "string",
162 "description": "Content slug - can be used directly with resource_loading tool as tyingshoelaces://blog/{slug}"
163 }
164 },
165 "required": ["title", "summary", "link"]
166 }
167 },
168 "count": {
169 "type": "integer",
170 "description": "Total number of items"
171 },
172 "_execution_id": {
173 "type": "string",
174 "description": "Execution ID for tracking"
175 }
176 },
177 "required": ["items"],
178 "x-artifact-type": "list"
179 })
180 }
181}