adk_ui/tools/
render_table.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 RenderTableParams {
14 #[serde(default)]
16 pub title: Option<String>,
17 pub columns: Vec<ColumnDef>,
19 #[serde(default)]
21 pub data: Vec<HashMap<String, Value>>,
22 #[serde(default)]
24 pub data_source: Option<DataSource>,
25 #[serde(default)]
26 pub sortable: bool,
27 #[serde(default)]
28 pub page_size: Option<u32>,
29 #[serde(default)]
30 pub striped: bool,
31 #[serde(flatten)]
33 pub protocol: LegacyProtocolOptions,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
37pub struct ColumnDef {
38 pub header: String,
40 pub accessor_key: String,
42 #[serde(default = "default_column_sortable")]
43 pub sortable: bool,
44}
45
46fn default_column_sortable() -> bool {
47 true
48}
49
50pub struct RenderTableTool;
72
73impl RenderTableTool {
74 pub fn new() -> Self {
75 Self
76 }
77}
78
79impl Default for RenderTableTool {
80 fn default() -> Self {
81 Self::new()
82 }
83}
84
85#[async_trait]
86impl Tool for RenderTableTool {
87 fn name(&self) -> &str {
88 "render_table"
89 }
90
91 fn description(&self) -> &str {
92 r#"Render a data table. Output example:
93┌───────┬─────────────────────┬───────┐
94│ Name │ Email │ Role │
95├───────┼─────────────────────┼───────┤
96│ Alice │ alice@example.com │ Admin │
97│ Bob │ bob@example.com │ User │
98└───────┴─────────────────────┴───────┘
99Set sortable=true for clickable column headers. Set page_size for pagination. Set striped=true for alternating row colors."#
100 }
101
102 fn parameters_schema(&self) -> Option<Value> {
103 Some(super::generate_gemini_schema::<RenderTableParams>())
104 }
105
106 async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
107 let params: RenderTableParams = serde_json::from_value(args)
108 .map_err(|e| crate::compat::AdkError::tool(format!("Invalid parameters: {}", e)))?;
109 if params.page_size == Some(0) {
110 return Err(crate::compat::AdkError::tool(
111 "Invalid parameters: page_size must be greater than zero".to_string(),
112 ));
113 }
114 if let Some(source) = ¶ms.data_source
115 && !source.binding_path.starts_with('/')
116 {
117 return Err(crate::compat::AdkError::tool(
118 "Invalid parameters: data_source.binding_path must be an absolute JSON Pointer"
119 .to_string(),
120 ));
121 }
122 let protocol_options = params.protocol.clone();
123
124 let columns: Vec<TableColumn> = params
125 .columns
126 .into_iter()
127 .map(|c| TableColumn {
128 header: c.header,
129 accessor_key: c.accessor_key,
130 sortable: c.sortable,
131 })
132 .collect();
133
134 let mut components = Vec::new();
135
136 if let Some(title) = params.title {
138 components.push(Component::Text(Text {
139 id: None,
140 content: title,
141 variant: TextVariant::H3,
142 }));
143 }
144
145 components.push(Component::Table(Table {
146 id: None,
147 columns,
148 data: params.data,
149 data_source: params.data_source,
150 sortable: params.sortable,
151 page_size: params.page_size,
152 striped: params.striped,
153 }));
154
155 let ui = UiResponse::new(components);
156 let surface_id = protocol_options.resolved_surface_id("table");
157 let output = render_ui_response_with_protocol(ui, &protocol_options, "table")?;
158 let surface_ref = crate::surface_runtime::next_surface_ref(&ctx, surface_id);
159 crate::surface_runtime::record_surface_ref(&ctx, &surface_ref);
160 Ok(output)
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use serde_json::json;
168
169 #[test]
170 fn parameters_expose_table_behavior() {
171 let params: RenderTableParams = serde_json::from_value(json!({
172 "columns": [{ "header": "Name", "accessor_key": "name", "sortable": false }],
173 "data": [],
174 "sortable": true,
175 "page_size": 25,
176 "striped": true
177 }))
178 .unwrap();
179 assert!(params.sortable);
180 assert_eq!(params.page_size, Some(25));
181 assert!(params.striped);
182 assert!(!params.columns[0].sortable);
183 }
184}