Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 125 additions & 93 deletions crates/bashkit/examples/scripted_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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<String, String> {
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<String, String> {
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<String, String> {
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<String, String> {
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"
))
}
}
Loading