pub mod column;
pub mod hints;
pub use column::Column;
pub use hints::TableHints;
use crate::artifacts::metadata::{ArtifactProvenance, ExecutionMetadata};
use crate::artifacts::traits::Artifact;
use crate::artifacts::types::ArtifactType;
use crate::execution::context::RequestContext;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::{Value as JsonValue, json};
use systemprompt_identifiers::{McpExecutionId, SkillId, SkillName};
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct TableResponse {
#[serde(rename = "x-artifact-type")]
pub artifact_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
pub columns: Vec<Column>,
pub items: Vec<JsonValue>,
pub count: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub execution_id: Option<McpExecutionId>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(with = "Option<JsonValue>")]
pub hints: Option<JsonValue>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct TableArtifact {
#[serde(rename = "x-artifact-type")]
#[serde(default = "default_artifact_type")]
pub artifact_type: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
pub columns: Vec<Column>,
pub items: Vec<JsonValue>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(with = "Option<JsonValue>")]
pub hints: Option<JsonValue>,
#[serde(skip)]
#[schemars(skip)]
hints_builder: TableHints,
#[serde(skip)]
#[schemars(skip)]
metadata: ArtifactProvenance,
}
fn default_artifact_type() -> String {
"table".to_owned()
}
impl TableArtifact {
pub const ARTIFACT_TYPE_STR: &'static str = "table";
pub fn new(columns: Vec<Column>) -> Self {
Self {
artifact_type: "table".to_owned(),
title: None,
columns,
items: Vec::new(),
hints: None,
hints_builder: TableHints::default(),
metadata: ArtifactProvenance::default(),
}
}
pub fn with_request(mut self, ctx: &RequestContext) -> Self {
self.metadata.set_request(ctx);
self
}
#[must_use]
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn with_rows(mut self, items: Vec<JsonValue>) -> Self {
self.items = items;
self
}
pub fn with_hints(mut self, hints: TableHints) -> Self {
use crate::artifacts::traits::ArtifactSchema;
self.hints = Some(hints.generate_schema());
self.hints_builder = hints;
self
}
pub fn with_metadata(mut self, metadata: ExecutionMetadata) -> Self {
self.metadata.set_metadata(metadata);
self
}
pub fn with_execution_id(mut self, id: McpExecutionId) -> Self {
self.metadata.set_execution_id(id);
self
}
pub fn with_skill(mut self, skill_id: SkillId, skill_name: SkillName) -> Self {
self.metadata.set_skill(skill_id, skill_name);
self
}
pub fn to_response(&self) -> JsonValue {
use crate::artifacts::traits::ArtifactSchema;
let response = TableResponse {
artifact_type: "table".to_owned(),
title: self.title.clone(),
columns: self.columns.clone(),
items: self.items.clone(),
count: self.items.len(),
execution_id: self.metadata.execution_id().cloned(),
hints: Some(self.hints_builder.generate_schema()),
};
match serde_json::to_value(response) {
Ok(v) => v,
Err(e) => {
tracing::error!(error = %e, "Failed to serialize table response");
JsonValue::Null
},
}
}
}
impl Artifact for TableArtifact {
fn artifact_type(&self) -> ArtifactType {
ArtifactType::Table
}
fn to_schema(&self) -> JsonValue {
use crate::artifacts::traits::ArtifactSchema;
json!({
"type": "object",
"properties": {
"columns": {
"type": "array",
"description": "Column definitions"
},
"items": {
"type": "array",
"description": "Array of data records"
},
"count": {
"type": "integer",
"description": "Total number of records"
},
"_execution_id": {
"type": "string",
"description": "Execution ID for tracking"
}
},
"required": ["columns", "items"],
"x-artifact-type": "table",
"x-table-hints": self.hints_builder.generate_schema()
})
}
}