Skip to main content

systemprompt_models/artifacts/table/
mod.rs

1pub mod column;
2pub mod hints;
3
4pub use column::Column;
5pub use hints::TableHints;
6
7use crate::artifacts::metadata::ExecutionMetadata;
8use crate::artifacts::traits::Artifact;
9use crate::artifacts::types::ArtifactType;
10use crate::execution::context::RequestContext;
11use schemars::JsonSchema;
12use serde::{Deserialize, Serialize};
13use serde_json::{Value as JsonValue, json};
14use systemprompt_identifiers::SkillId;
15
16#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
17pub struct TableResponse {
18    #[serde(rename = "x-artifact-type")]
19    pub artifact_type: String,
20    pub columns: Vec<Column>,
21    pub items: Vec<JsonValue>,
22    pub count: usize,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub execution_id: Option<String>,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    #[schemars(with = "Option<JsonValue>")]
27    pub hints: Option<JsonValue>,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
31pub struct TableArtifact {
32    #[serde(rename = "x-artifact-type")]
33    #[serde(default = "default_artifact_type")]
34    pub artifact_type: String,
35    pub columns: Vec<Column>,
36    pub items: Vec<JsonValue>,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    #[schemars(with = "Option<JsonValue>")]
39    pub hints: Option<JsonValue>,
40    #[serde(skip)]
41    #[schemars(skip)]
42    hints_builder: TableHints,
43    #[serde(skip)]
44    #[schemars(skip)]
45    metadata: ExecutionMetadata,
46}
47
48fn default_artifact_type() -> String {
49    "table".to_string()
50}
51
52impl TableArtifact {
53    pub const ARTIFACT_TYPE_STR: &'static str = "table";
54
55    pub fn new(columns: Vec<Column>, ctx: &RequestContext) -> Self {
56        Self {
57            artifact_type: "table".to_string(),
58            columns,
59            items: Vec::new(),
60            hints: None,
61            hints_builder: TableHints::default(),
62            metadata: ExecutionMetadata::with_request(ctx),
63        }
64    }
65
66    pub fn with_rows(mut self, items: Vec<JsonValue>) -> Self {
67        self.items = items;
68        self
69    }
70
71    pub fn with_hints(mut self, hints: TableHints) -> Self {
72        use crate::artifacts::traits::ArtifactSchema;
73        self.hints = Some(hints.generate_schema());
74        self.hints_builder = hints;
75        self
76    }
77
78    pub fn with_metadata(mut self, metadata: ExecutionMetadata) -> Self {
79        self.metadata = metadata;
80        self
81    }
82
83    pub fn with_execution_id(mut self, id: impl Into<String>) -> Self {
84        self.metadata.execution_id = Some(id.into());
85        self
86    }
87
88    pub fn with_skill(
89        mut self,
90        skill_id: impl Into<SkillId>,
91        skill_name: impl Into<String>,
92    ) -> Self {
93        self.metadata.skill_id = Some(skill_id.into());
94        self.metadata.skill_name = Some(skill_name.into());
95        self
96    }
97
98    pub fn to_response(&self) -> JsonValue {
99        use crate::artifacts::traits::ArtifactSchema;
100
101        let response = TableResponse {
102            artifact_type: "table".to_string(),
103            columns: self.columns.clone(),
104            items: self.items.clone(),
105            count: self.items.len(),
106            execution_id: self.metadata.execution_id.clone(),
107            hints: Some(self.hints_builder.generate_schema()),
108        };
109        match serde_json::to_value(response) {
110            Ok(v) => v,
111            Err(e) => {
112                tracing::error!(error = %e, "Failed to serialize table response");
113                JsonValue::Null
114            },
115        }
116    }
117}
118
119impl Artifact for TableArtifact {
120    fn artifact_type(&self) -> ArtifactType {
121        ArtifactType::Table
122    }
123
124    fn to_schema(&self) -> JsonValue {
125        use crate::artifacts::traits::ArtifactSchema;
126
127        json!({
128            "type": "object",
129            "properties": {
130                "columns": {
131                    "type": "array",
132                    "description": "Column definitions"
133                },
134                "items": {
135                    "type": "array",
136                    "description": "Array of data records"
137                },
138                "count": {
139                    "type": "integer",
140                    "description": "Total number of records"
141                },
142                "_execution_id": {
143                    "type": "string",
144                    "description": "Execution ID for tracking"
145                }
146            },
147            "required": ["columns", "items"],
148            "x-artifact-type": "table",
149            "x-table-hints": self.hints_builder.generate_schema()
150        })
151    }
152}