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