use crate::knowledge::Knowledge;
use adaptive_card_core::{CardVersion, Host, Presentation, check_accessibility, validate_card};
use serde_json::{Value, json};
use std::sync::Arc;
pub async fn dispatch(kb: &Arc<Knowledge>, name: &str, args: &str) -> String {
match dispatch_inner(kb, name, args).await {
Ok(value) => serde_json::to_string(&value)
.unwrap_or_else(|e| format!(r#"{{"error":"serialize: {e}"}}"#)),
Err(e) => json!({ "error": e.to_string() }).to_string(),
}
}
async fn dispatch_inner(kb: &Arc<Knowledge>, name: &str, args: &str) -> anyhow::Result<Value> {
let parsed: Value = serde_json::from_str(args)
.map_err(|e| anyhow::anyhow!("invalid tool arguments JSON: {e}"))?;
match name {
"validate_card" => {
let card = parsed
.get("card")
.ok_or_else(|| anyhow::anyhow!("missing required 'card'"))?;
let host = parsed
.get("host")
.and_then(Value::as_str)
.and_then(Host::from_str);
Ok(serde_json::to_value(validate_card(card, host))?)
}
"analyze_card" => {
let card = parsed
.get("card")
.ok_or_else(|| anyhow::anyhow!("missing required 'card'"))?;
Ok(serde_json::to_value(adaptive_card_core::analyze_card(
card,
))?)
}
"check_accessibility" => {
let card = parsed
.get("card")
.ok_or_else(|| anyhow::anyhow!("missing required 'card'"))?;
Ok(serde_json::to_value(check_accessibility(card))?)
}
"suggest_layout" => {
let query = parsed
.get("query")
.and_then(Value::as_str)
.ok_or_else(|| anyhow::anyhow!("missing required 'query'"))?;
let limit = parsed
.get("limit")
.and_then(Value::as_u64)
.map(|n| n as usize)
.unwrap_or(5);
Ok(serde_json::to_value(kb.suggest(query, limit))?)
}
"optimize_card" => {
let card = parsed
.get("card")
.cloned()
.ok_or_else(|| anyhow::anyhow!("missing required 'card'"))?;
let opts = adaptive_card_core::OptimizeOpts {
accessibility: parsed
.get("accessibility")
.and_then(Value::as_bool)
.unwrap_or(false),
performance: parsed
.get("performance")
.and_then(Value::as_bool)
.unwrap_or(false),
modernize: parsed
.get("modernize")
.and_then(Value::as_bool)
.unwrap_or(false),
target_host: parsed
.get("target_host")
.and_then(Value::as_str)
.and_then(Host::from_str),
};
Ok(serde_json::to_value(adaptive_card_core::optimize_card(
card, &opts,
))?)
}
"transform_card" => {
let card = parsed
.get("card")
.cloned()
.ok_or_else(|| anyhow::anyhow!("missing required 'card'"))?;
let target = adaptive_card_core::TransformTarget {
version: parsed
.get("target_version")
.and_then(Value::as_str)
.and_then(CardVersion::parse),
host: parsed
.get("target_host")
.and_then(Value::as_str)
.and_then(Host::from_str),
strict: parsed
.get("strict")
.and_then(Value::as_bool)
.unwrap_or(false),
};
Ok(serde_json::to_value(adaptive_card_core::transform_card(
card, &target,
)?)?)
}
"template_card" => {
let card = parsed
.get("card")
.cloned()
.ok_or_else(|| anyhow::anyhow!("missing required 'card'"))?;
Ok(serde_json::to_value(adaptive_card_core::template_card(
card,
))?)
}
"data_to_card" => {
let data = parsed
.get("data")
.ok_or_else(|| anyhow::anyhow!("missing required 'data'"))?;
let host = parsed
.get("host")
.and_then(Value::as_str)
.and_then(Host::from_str)
.unwrap_or(Host::Generic);
let presentation = parsed
.get("presentation")
.and_then(Value::as_str)
.and_then(|s| match s {
"table" => Some(Presentation::Table),
"factset" => Some(Presentation::FactSet),
"list" => Some(Presentation::List),
"chart" => Some(Presentation::Chart),
"auto" => Some(Presentation::Auto),
_ => None,
});
let title = parsed
.get("title")
.and_then(Value::as_str)
.map(String::from);
let opts = adaptive_card_core::DataToCardOpts {
title,
presentation,
host,
};
Ok(adaptive_card_core::data_to_card(data, &opts)?)
}
"list_examples" => {
let category = parsed.get("category").and_then(Value::as_str);
let limit = parsed
.get("limit")
.and_then(Value::as_u64)
.map(|n| n as usize)
.unwrap_or(20);
let entries: Vec<Value> = kb
.all()
.iter()
.filter(|e| category.is_none_or(|c| e.category == c))
.take(limit)
.map(|e| {
json!({
"id": e.id,
"title": e.title,
"category": e.category,
"tags": e.tags,
"complexity": format!("{:?}", e.complexity).to_lowercase(),
})
})
.collect();
Ok(json!({ "examples": entries }))
}
"get_example" => {
let id = parsed
.get("id")
.and_then(Value::as_str)
.ok_or_else(|| anyhow::anyhow!("missing required 'id'"))?;
let entry = kb
.by_id(id)
.ok_or_else(|| anyhow::anyhow!("knowledge entry not found: {id}"))?;
Ok(serde_json::to_value(entry)?)
}
"pack_card" => {
Ok(json!({
"error": "pack_card is not available as an LLM tool. Use the Pack button in the UI instead."
}))
}
"deploy_pack" => {
Ok(json!({
"error": "deploy_pack is not available as an LLM tool. Use the Pack button with 'Build .gtbundle' option in the UI instead."
}))
}
other => anyhow::bail!("unknown tool: {other}"),
}
}