systemprompt_models/artifacts/table/
mod.rs1pub mod column;
11pub mod hints;
12
13pub use column::Column;
14pub use hints::TableHints;
15
16use crate::artifacts::metadata::ExecutionMetadata;
17use crate::artifacts::traits::Artifact;
18use crate::artifacts::types::ArtifactType;
19use crate::execution::context::RequestContext;
20use schemars::JsonSchema;
21use serde::{Deserialize, Serialize};
22use serde_json::{Value as JsonValue, json};
23use systemprompt_identifiers::SkillId;
24
25#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
26pub struct TableResponse {
27 #[serde(rename = "x-artifact-type")]
28 pub artifact_type: String,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub title: Option<String>,
31 pub columns: Vec<Column>,
32 pub items: Vec<JsonValue>,
34 pub count: usize,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub execution_id: Option<String>,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 #[schemars(with = "Option<JsonValue>")]
39 pub hints: Option<JsonValue>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
44pub struct TableArtifact {
45 #[serde(rename = "x-artifact-type")]
46 #[serde(default = "default_artifact_type")]
47 pub artifact_type: String,
48 #[serde(default, skip_serializing_if = "Option::is_none")]
49 pub title: Option<String>,
50 pub columns: Vec<Column>,
51 pub items: Vec<JsonValue>,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 #[schemars(with = "Option<JsonValue>")]
55 pub hints: Option<JsonValue>,
57 #[serde(skip)]
58 #[schemars(skip)]
59 hints_builder: TableHints,
60 #[serde(skip)]
61 #[schemars(skip)]
62 metadata: ExecutionMetadata,
63}
64
65fn default_artifact_type() -> String {
66 "table".to_owned()
67}
68
69impl TableArtifact {
70 pub const ARTIFACT_TYPE_STR: &'static str = "table";
71
72 pub fn new(columns: Vec<Column>) -> Self {
73 Self {
74 artifact_type: "table".to_owned(),
75 title: None,
76 columns,
77 items: Vec::new(),
78 hints: None,
79 hints_builder: TableHints::default(),
80 metadata: ExecutionMetadata::default(),
81 }
82 }
83
84 pub fn with_request(mut self, ctx: &RequestContext) -> Self {
85 self.metadata = ExecutionMetadata::with_request(ctx);
86 self
87 }
88
89 #[must_use]
90 pub fn with_title(mut self, title: impl Into<String>) -> Self {
91 self.title = Some(title.into());
92 self
93 }
94
95 pub fn with_rows(mut self, items: Vec<JsonValue>) -> Self {
97 self.items = items;
98 self
99 }
100
101 pub fn with_hints(mut self, hints: TableHints) -> Self {
102 use crate::artifacts::traits::ArtifactSchema;
103 self.hints = Some(hints.generate_schema());
104 self.hints_builder = hints;
105 self
106 }
107
108 pub fn with_metadata(mut self, metadata: ExecutionMetadata) -> Self {
109 self.metadata = metadata;
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 pub fn to_response(&self) -> JsonValue {
130 use crate::artifacts::traits::ArtifactSchema;
131
132 let response = TableResponse {
133 artifact_type: "table".to_owned(),
134 title: self.title.clone(),
135 columns: self.columns.clone(),
136 items: self.items.clone(),
137 count: self.items.len(),
138 execution_id: self.metadata.execution_id.clone(),
139 hints: Some(self.hints_builder.generate_schema()),
140 };
141 match serde_json::to_value(response) {
142 Ok(v) => v,
143 Err(e) => {
144 tracing::error!(error = %e, "Failed to serialize table response");
145 JsonValue::Null
146 },
147 }
148 }
149}
150
151impl Artifact for TableArtifact {
152 fn artifact_type(&self) -> ArtifactType {
153 ArtifactType::Table
154 }
155
156 fn to_schema(&self) -> JsonValue {
158 use crate::artifacts::traits::ArtifactSchema;
159
160 json!({
161 "type": "object",
162 "properties": {
163 "columns": {
164 "type": "array",
165 "description": "Column definitions"
166 },
167 "items": {
168 "type": "array",
169 "description": "Array of data records"
170 },
171 "count": {
172 "type": "integer",
173 "description": "Total number of records"
174 },
175 "_execution_id": {
176 "type": "string",
177 "description": "Execution ID for tracking"
178 }
179 },
180 "required": ["columns", "items"],
181 "x-artifact-type": "table",
182 "x-table-hints": self.hints_builder.generate_schema()
183 })
184 }
185}