adk_ui/tools/
render_chart.rs1use crate::compat::{Result, Tool, ToolContext};
2use crate::schema::*;
3use crate::tools::{LegacyProtocolOptions, render_ui_response_with_protocol};
4use async_trait::async_trait;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::collections::HashMap;
9use std::sync::Arc;
10
11#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
13pub struct RenderChartParams {
14 #[serde(default)]
16 pub title: Option<String>,
17 #[serde(rename = "type", default = "default_chart_type")]
19 pub chart_type: String,
20 #[serde(default)]
22 pub data: Vec<HashMap<String, Value>>,
23 #[serde(default)]
24 pub data_source: Option<DataSource>,
25 pub x_key: String,
27 pub y_keys: Vec<String>,
29 #[serde(default)]
30 pub x_type: ChartXType,
31 #[serde(default)]
32 pub time_format: Option<String>,
33 #[serde(default)]
34 pub window: Option<u32>,
35 #[serde(default)]
36 pub x_label: Option<String>,
37 #[serde(default)]
38 pub y_label: Option<String>,
39 #[serde(default = "default_show_legend")]
40 pub show_legend: bool,
41 #[serde(default)]
42 pub colors: Option<Vec<String>>,
43 #[serde(flatten)]
45 pub protocol: LegacyProtocolOptions,
46}
47
48fn default_chart_type() -> String {
49 "bar".to_string()
50}
51
52fn default_show_legend() -> bool {
53 true
54}
55
56pub struct RenderChartTool;
84
85impl RenderChartTool {
86 pub fn new() -> Self {
87 Self
88 }
89}
90
91impl Default for RenderChartTool {
92 fn default() -> Self {
93 Self::new()
94 }
95}
96
97#[async_trait]
98impl Tool for RenderChartTool {
99 fn name(&self) -> &str {
100 "render_chart"
101 }
102
103 fn description(&self) -> &str {
104 "Render a chart to visualize data. Supports bar, line, area, and pie charts. Use this for showing trends, comparisons, or distributions."
105 }
106
107 fn parameters_schema(&self) -> Option<Value> {
108 Some(super::generate_gemini_schema::<RenderChartParams>())
109 }
110
111 async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
112 let params: RenderChartParams = serde_json::from_value(args)
113 .map_err(|e| crate::compat::AdkError::tool(format!("Invalid parameters: {}", e)))?;
114 if params.window == Some(0) {
115 return Err(crate::compat::AdkError::tool(
116 "Invalid parameters: window must be greater than zero".to_string(),
117 ));
118 }
119 if let Some(source) = ¶ms.data_source
120 && !source.binding_path.starts_with('/')
121 {
122 return Err(crate::compat::AdkError::tool(
123 "Invalid parameters: data_source.binding_path must be an absolute JSON Pointer"
124 .to_string(),
125 ));
126 }
127 let protocol_options = params.protocol.clone();
128
129 let kind = match params.chart_type.as_str() {
130 "line" => ChartKind::Line,
131 "area" => ChartKind::Area,
132 "pie" => ChartKind::Pie,
133 "scatter" => ChartKind::Scatter,
134 "sparkline" => ChartKind::Sparkline,
135 _ => ChartKind::Bar,
136 };
137
138 let ui = UiResponse::new(vec![Component::Chart(Chart {
139 id: None,
140 title: params.title,
141 kind,
142 data: params.data,
143 data_source: params.data_source,
144 x_key: params.x_key,
145 y_keys: params.y_keys,
146 x_type: params.x_type,
147 time_format: params.time_format,
148 window: params.window,
149 x_label: params.x_label,
150 y_label: params.y_label,
151 show_legend: params.show_legend,
152 colors: params.colors,
153 })]);
154
155 let surface_id = protocol_options.resolved_surface_id("chart");
156 let output = render_ui_response_with_protocol(ui, &protocol_options, "chart")?;
157 let surface_ref = crate::surface_runtime::next_surface_ref(&ctx, surface_id);
158 crate::surface_runtime::record_surface_ref(&ctx, &surface_ref);
159 Ok(output)
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166 use serde_json::json;
167
168 #[test]
169 fn parameters_expose_time_series_behavior() {
170 let params: RenderChartParams = serde_json::from_value(json!({
171 "type": "sparkline",
172 "x_key": "at",
173 "y_keys": ["latency"],
174 "x_type": "time",
175 "time_format": "time",
176 "window": 60,
177 "x_label": "Observed",
178 "y_label": "Milliseconds",
179 "show_legend": false,
180 "colors": ["#123456"]
181 }))
182 .unwrap();
183 assert!(matches!(params.x_type, ChartXType::Time));
184 assert_eq!(params.window, Some(60));
185 assert!(!params.show_legend);
186 }
187}