diff --git a/crates/bashkit/examples/scripted_tool.rs b/crates/bashkit/examples/scripted_tool.rs index 5cbb8684..d9551f35 100644 --- a/crates/bashkit/examples/scripted_tool.rs +++ b/crates/bashkit/examples/scripted_tool.rs @@ -8,106 +8,20 @@ //! This example simulates an e-commerce API with tools for users, orders, and //! inventory. The ScriptedTool lets an agent compose these in one call. -use bashkit::{ScriptedTool, Tool, ToolDef, ToolRequest}; +use bashkit::{ScriptedTool, Tool, ToolRequest}; #[tokio::main] async fn main() -> anyhow::Result<()> { println!("=== Scripted Tool Demo ===\n"); - // Build the orchestrator with tool definitions + closures + // Build the orchestrator with tool definitions + callbacks. + // In production the callbacks would call real APIs. let mut tool = ScriptedTool::builder("ecommerce_api") .short_description("E-commerce API orchestrator with user, order, and inventory tools") - .tool( - ToolDef::new("get_user", "Fetch user by ID") - .with_schema(serde_json::json!({ - "type": "object", - "properties": { - "id": {"type": "integer", "description": "User ID"} - }, - "required": ["id"] - })), - |args| { - let id = args.param_i64("id").ok_or("missing --id")?; - - let users = [ - (1, "Alice", "alice@example.com", "premium"), - (2, "Bob", "bob@example.com", "basic"), - (3, "Charlie", "charlie@example.com", "premium"), - ]; - - match users.iter().find(|(uid, ..)| *uid == id) { - Some((uid, name, email, tier)) => Ok(format!( - "{{\"id\":{uid},\"name\":\"{name}\",\"email\":\"{email}\",\"tier\":\"{tier}\"}}\n" - )), - None => Err(format!("user {} not found", id)), - } - }, - ) - .tool( - ToolDef::new("list_orders", "List orders for a user") - .with_schema(serde_json::json!({ - "type": "object", - "properties": { - "user_id": {"type": "integer", "description": "User ID"} - }, - "required": ["user_id"] - })), - |args| { - let uid = args.param_i64("user_id").ok_or("missing --user_id")?; - - let orders = match uid { - 1 => r#"[{"order_id":101,"item":"Laptop","qty":1,"price":999.99},{"order_id":102,"item":"Mouse","qty":2,"price":29.99}]"#, - 2 => r#"[{"order_id":201,"item":"Keyboard","qty":1,"price":79.99}]"#, - 3 => r#"[]"#, - _ => return Err(format!("no orders for user {}", uid)), - }; - - Ok(format!("{orders}\n")) - }, - ) - .tool( - ToolDef::new("get_inventory", "Check inventory for an item") - .with_schema(serde_json::json!({ - "type": "object", - "properties": { - "item": {"type": "string", "description": "Item name"} - }, - "required": ["item"] - })), - |args| { - let item = args.param_str("item").ok_or("missing --item")?; - - let stock = match item.to_lowercase().as_str() { - "laptop" => 15, - "mouse" => 142, - "keyboard" => 67, - _ => 0, - }; - - Ok(format!( - "{{\"item\":\"{}\",\"in_stock\":{}}}\n", - item, stock - )) - }, - ) - .tool( - ToolDef::new("create_discount", "Create a discount code") - .with_schema(serde_json::json!({ - "type": "object", - "properties": { - "user_id": {"type": "integer", "description": "User ID"}, - "percent": {"type": "integer", "description": "Discount percentage"} - }, - "required": ["user_id", "percent"] - })), - |args| { - let uid = args.param_i64("user_id").ok_or("missing --user_id")?; - let pct = args.param_i64("percent").ok_or("missing --percent")?; - Ok(format!( - "{{\"code\":\"SAVE{pct}-U{uid}\",\"percent\":{pct},\"user_id\":{uid}}}\n" - )) - }, - ) + .tool(fakes::get_user_def(), fakes::get_user) + .tool(fakes::list_orders_def(), fakes::list_orders) + .tool(fakes::get_inventory_def(), fakes::get_inventory) + .tool(fakes::create_discount_def(), fakes::create_discount) .env("STORE_NAME", "Bashkit Shop") .build(); @@ -227,3 +141,121 @@ async fn main() -> anyhow::Result<()> { println!("\n=== Demo Complete ==="); Ok(()) } + +// --------------------------------------------------------------------------- +// Fake e-commerce API — tool definitions + handlers +// +// In a real application these callbacks would call actual HTTP APIs, databases, +// etc. They live in a separate module so the main function can focus on the +// ScriptedTool builder API and the demo scenarios. +// --------------------------------------------------------------------------- +mod fakes { + use bashkit::{ToolArgs, ToolDef}; + + // -- get_user -------------------------------------------------------- + + pub fn get_user_def() -> ToolDef { + ToolDef::new("get_user", "Fetch user by ID").with_schema(serde_json::json!({ + "type": "object", + "properties": { + "id": {"type": "integer", "description": "User ID"} + }, + "required": ["id"] + })) + } + + pub fn get_user(args: &ToolArgs) -> Result { + let id = args.param_i64("id").ok_or("missing --id")?; + + let users = [ + (1, "Alice", "alice@example.com", "premium"), + (2, "Bob", "bob@example.com", "basic"), + (3, "Charlie", "charlie@example.com", "premium"), + ]; + + match users.iter().find(|(uid, ..)| *uid == id) { + Some((uid, name, email, tier)) => Ok(format!( + "{{\"id\":{uid},\"name\":\"{name}\",\"email\":\"{email}\",\"tier\":\"{tier}\"}}\n" + )), + None => Err(format!("user {} not found", id)), + } + } + + // -- list_orders ----------------------------------------------------- + + pub fn list_orders_def() -> ToolDef { + ToolDef::new("list_orders", "List orders for a user").with_schema(serde_json::json!({ + "type": "object", + "properties": { + "user_id": {"type": "integer", "description": "User ID"} + }, + "required": ["user_id"] + })) + } + + pub fn list_orders(args: &ToolArgs) -> Result { + let uid = args.param_i64("user_id").ok_or("missing --user_id")?; + + let orders = match uid { + 1 => { + r#"[{"order_id":101,"item":"Laptop","qty":1,"price":999.99},{"order_id":102,"item":"Mouse","qty":2,"price":29.99}]"# + } + 2 => r#"[{"order_id":201,"item":"Keyboard","qty":1,"price":79.99}]"#, + 3 => r#"[]"#, + _ => return Err(format!("no orders for user {}", uid)), + }; + + Ok(format!("{orders}\n")) + } + + // -- get_inventory --------------------------------------------------- + + pub fn get_inventory_def() -> ToolDef { + ToolDef::new("get_inventory", "Check inventory for an item").with_schema( + serde_json::json!({ + "type": "object", + "properties": { + "item": {"type": "string", "description": "Item name"} + }, + "required": ["item"] + }), + ) + } + + pub fn get_inventory(args: &ToolArgs) -> Result { + let item = args.param_str("item").ok_or("missing --item")?; + + let stock = match item.to_lowercase().as_str() { + "laptop" => 15, + "mouse" => 142, + "keyboard" => 67, + _ => 0, + }; + + Ok(format!( + "{{\"item\":\"{}\",\"in_stock\":{}}}\n", + item, stock + )) + } + + // -- create_discount ------------------------------------------------- + + pub fn create_discount_def() -> ToolDef { + ToolDef::new("create_discount", "Create a discount code").with_schema(serde_json::json!({ + "type": "object", + "properties": { + "user_id": {"type": "integer", "description": "User ID"}, + "percent": {"type": "integer", "description": "Discount percentage"} + }, + "required": ["user_id", "percent"] + })) + } + + pub fn create_discount(args: &ToolArgs) -> Result { + let uid = args.param_i64("user_id").ok_or("missing --user_id")?; + let pct = args.param_i64("percent").ok_or("missing --percent")?; + Ok(format!( + "{{\"code\":\"SAVE{pct}-U{uid}\",\"percent\":{pct},\"user_id\":{uid}}}\n" + )) + } +}