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>,
33 pub count: usize,
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub execution_id: Option<String>,
36 #[serde(skip_serializing_if = "Option::is_none")]
37 #[schemars(with = "Option<JsonValue>")]
38 pub hints: Option<JsonValue>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
42pub struct TableArtifact {
43 #[serde(rename = "x-artifact-type")]
44 #[serde(default = "default_artifact_type")]
45 pub artifact_type: String,
46 #[serde(default, skip_serializing_if = "Option::is_none")]
47 pub title: Option<String>,
48 pub columns: Vec<Column>,
49 pub items: Vec<JsonValue>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 #[schemars(with = "Option<JsonValue>")]
52 pub hints: Option<JsonValue>,
53 #[serde(skip)]
54 #[schemars(skip)]
55 hints_builder: TableHints,
56 #[serde(skip)]
57 #[schemars(skip)]
58 metadata: ExecutionMetadata,
59}
60
61fn default_artifact_type() -> String {
62 "table".to_owned()
63}
64
65impl TableArtifact {
66 pub const ARTIFACT_TYPE_STR: &'static str = "table";
67
68 pub fn new(columns: Vec<Column>) -> Self {
69 Self {
70 artifact_type: "table".to_owned(),
71 title: None,
72 columns,
73 items: Vec::new(),
74 hints: None,
75 hints_builder: TableHints::default(),
76 metadata: ExecutionMetadata::default(),
77 }
78 }
79
80 pub fn with_request(mut self, ctx: &RequestContext) -> Self {
81 self.metadata = ExecutionMetadata::with_request(ctx);
82 self
83 }
84
85 #[must_use]
86 pub fn with_title(mut self, title: impl Into<String>) -> Self {
87 self.title = Some(title.into());
88 self
89 }
90
91 pub fn with_rows(mut self, items: Vec<JsonValue>) -> Self {
92 self.items = items;
93 self
94 }
95
96 pub fn with_hints(mut self, hints: TableHints) -> Self {
97 use crate::artifacts::traits::ArtifactSchema;
98 self.hints = Some(hints.generate_schema());
99 self.hints_builder = hints;
100 self
101 }
102
103 pub fn with_metadata(mut self, metadata: ExecutionMetadata) -> Self {
104 self.metadata = metadata;
105 self
106 }
107
108 pub fn with_execution_id(mut self, id: impl Into<String>) -> Self {
109 self.metadata.execution_id = Some(id.into());
110 self
111 }
112
113 pub fn with_skill(
114 mut self,
115 skill_id: impl Into<SkillId>,
116 skill_name: impl Into<String>,
117 ) -> Self {
118 self.metadata.skill_id = Some(skill_id.into());
119 self.metadata.skill_name = Some(skill_name.into());
120 self
121 }
122
123 pub fn to_response(&self) -> JsonValue {
124 use crate::artifacts::traits::ArtifactSchema;
125
126 let response = TableResponse {
127 artifact_type: "table".to_owned(),
128 title: self.title.clone(),
129 columns: self.columns.clone(),
130 items: self.items.clone(),
131 count: self.items.len(),
132 execution_id: self.metadata.execution_id.clone(),
133 hints: Some(self.hints_builder.generate_schema()),
134 };
135 match serde_json::to_value(response) {
136 Ok(v) => v,
137 Err(e) => {
138 tracing::error!(error = %e, "Failed to serialize table response");
139 JsonValue::Null
140 },
141 }
142 }
143}
144
145impl Artifact for TableArtifact {
146 fn artifact_type(&self) -> ArtifactType {
147 ArtifactType::Table
148 }
149
150 fn to_schema(&self) -> JsonValue {
151 use crate::artifacts::traits::ArtifactSchema;
152
153 json!({
154 "type": "object",
155 "properties": {
156 "columns": {
157 "type": "array",
158 "description": "Column definitions"
159 },
160 "items": {
161 "type": "array",
162 "description": "Array of data records"
163 },
164 "count": {
165 "type": "integer",
166 "description": "Total number of records"
167 },
168 "_execution_id": {
169 "type": "string",
170 "description": "Execution ID for tracking"
171 }
172 },
173 "required": ["columns", "items"],
174 "x-artifact-type": "table",
175 "x-table-hints": self.hints_builder.generate_schema()
176 })
177 }
178}