use crate::error::ArtifactError;
use serde_json::{Value as JsonValue, json};
use systemprompt_models::{ArtifactMetadata, ContextId, TaskId};
use systemprompt_models::artifacts::types::ArtifactType;
use super::artifact_type_to_string;
#[derive(Debug)]
pub struct BuildMetadataParams<'a> {
pub artifact_type: &'a ArtifactType,
pub schema: Option<&'a JsonValue>,
pub mcp_execution_id: Option<String>,
pub context_id: &'a str,
pub task_id: &'a str,
pub tool_name: &'a str,
}
pub fn build_metadata(params: BuildMetadataParams<'_>) -> Result<ArtifactMetadata, ArtifactError> {
let BuildMetadataParams {
artifact_type,
schema,
mcp_execution_id,
context_id,
task_id,
tool_name,
} = params;
let rendering_hints = match artifact_type {
ArtifactType::Table => extract_table_hints(schema),
ArtifactType::Form => extract_form_hints(schema),
ArtifactType::Chart => extract_chart_hints(schema),
ArtifactType::PresentationCard => extract_presentation_hints(schema),
ArtifactType::Dashboard => extract_dashboard_hints(schema),
_ => json!(null),
};
let context_id_typed = ContextId::new(context_id);
let task_id_typed = TaskId::new(task_id);
let mut metadata = ArtifactMetadata::new_validated(
artifact_type_to_string(artifact_type),
context_id_typed,
task_id_typed,
)
.map_err(|e| ArtifactError::MetadataValidation(format!("{e}")))?;
metadata = metadata.with_tool_name(tool_name.to_string());
if !rendering_hints.is_null() {
metadata = metadata.with_rendering_hints(rendering_hints);
}
if let Some(schema) = schema {
metadata = metadata.with_mcp_schema(schema.clone());
}
if let Some(execution_id) = mcp_execution_id {
metadata = metadata.with_mcp_execution_id(execution_id);
}
Ok(metadata)
}
fn extract_table_hints(schema: Option<&JsonValue>) -> JsonValue {
if let Some(schema) = schema
&& let Some(hints) = schema.get("x-table-hints")
{
return hints.clone();
}
if let Some(schema) = schema
&& let Some(items) = schema.get("items")
&& let Some(properties) = items.get("properties")
&& let Some(props_obj) = properties.as_object()
{
let columns: Vec<String> = props_obj.keys().cloned().collect();
return json!({
"columns": columns,
"sortable_columns": columns,
"filterable": true,
"page_size": 25,
});
}
json!({})
}
fn extract_form_hints(schema: Option<&JsonValue>) -> JsonValue {
if let Some(schema) = schema
&& let Some(hints) = schema.get("x-form-hints")
{
return hints.clone();
}
if let Some(schema) = schema
&& let Some(properties) = schema.get("properties")
{
let fields = schema_properties_to_form_fields(properties);
return json!({
"fields": fields,
"layout": "vertical",
});
}
json!({})
}
fn extract_chart_hints(schema: Option<&JsonValue>) -> JsonValue {
if let Some(schema) = schema
&& let Some(hints) = schema.get("x-chart-hints")
{
return hints.clone();
}
json!({
"chart_type": "bar",
})
}
fn extract_presentation_hints(schema: Option<&JsonValue>) -> JsonValue {
if let Some(schema) = schema
&& let Some(hints) = schema.get("x-presentation-hints")
{
return hints.clone();
}
json!({
"theme": "default"
})
}
fn extract_dashboard_hints(schema: Option<&JsonValue>) -> JsonValue {
if let Some(schema) = schema
&& let Some(hints) = schema.get("x-dashboard-hints")
{
return hints.clone();
}
json!({
"layout": "vertical"
})
}
fn schema_properties_to_form_fields(properties: &JsonValue) -> Vec<JsonValue> {
let mut fields = Vec::new();
if let Some(props_obj) = properties.as_object() {
for (name, prop_schema) in props_obj {
let field_type = schema_type_to_form_type(prop_schema);
let mut field = json!({
"name": name,
"type": field_type,
"label": name,
});
if let Some(description) = prop_schema.get("description") {
field["help_text"] = description.clone();
}
if let Some(enum_vals) = prop_schema.get("enum") {
field["options"] = enum_vals.clone();
}
if let Some(default) = prop_schema.get("default") {
field["default"] = default.clone();
}
fields.push(field);
}
}
fields
}
fn schema_type_to_form_type(prop_schema: &JsonValue) -> &str {
if let Some(format) = prop_schema.get("format").and_then(|f| f.as_str()) {
return match format {
"email" => "email",
"date" => "date",
"date-time" => "datetime",
"password" => "password",
_ => "text",
};
}
if prop_schema.get("enum").is_some() {
return "select";
}
match prop_schema.get("type").and_then(|t| t.as_str()) {
Some("integer" | "number") => "number",
Some("boolean") => "checkbox",
_ => "text",
}
}