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