Skip to main content

systemprompt_models/artifacts/table/
mod.rs

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