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