Expand description
Scripted tool: compose ToolDef+callback pairs into a single Tool via bash scripts.
Requires the scripted_tool feature.
Scripted tool
Compose tool definitions + callbacks into a single Tool that accepts bash
scripts. Each registered tool becomes a builtin command inside the interpreter,
so an LLM can orchestrate many operations in one call using pipes, variables,
loops, and conditionals.
This module follows the same contract surface as crate::tool:
ScriptedToolBuilder::build-> immutable metadata objectScriptedToolBuilder::build_service->tower::Service<Value, Value>Tool::execution-> validated, single-usecrate::ToolExecutionTool::help-> Markdown docsTool::system_prompt-> terse plain-text instructions
§Architecture
┌─────────────────────────────────────────┐
│ ScriptedTool (implements Tool) │
│ │
│ ┌─────────┐ ┌─────────┐ ┌──────────┐ │
│ │get_user │ │get_order│ │inventory │ │
│ │(builtin)│ │(builtin)│ │(builtin) │ │
│ └─────────┘ └─────────┘ └──────────┘ │
│ ↑ ↑ ↑ │
│ bash script: pipes, vars, jq, loops │
└─────────────────────────────────────────┘§Example
use bashkit::{ScriptedTool, Tool, ToolArgs, ToolDef};
let tool = ScriptedTool::builder("api")
.tool_fn(
ToolDef::new("greet", "Greet a user")
.with_schema(serde_json::json!({
"type": "object",
"properties": { "name": {"type": "string"} }
})),
|args: &ToolArgs| {
let name = args.param_str("name").unwrap_or("world");
Ok(format!("hello {name}\n"))
},
)
.build();
let output = tool
.execution(serde_json::json!({"commands": "greet --name Alice"}))
.expect("valid args")
.execute()
.await
.expect("execution succeeds");
assert_eq!(output.result["stdout"], "hello Alice\n");
assert!(tool.help().contains("## Tool Commands"));§Shared context across callbacks
When multiple tool callbacks need shared resources (HTTP clients, auth tokens,
config), use the standard Rust closure-capture pattern with Arc:
use bashkit::{ScriptedTool, ToolArgs, ToolDef};
use std::sync::Arc;
let api_key = Arc::new("sk-secret-key".to_string());
let base_url = Arc::new("https://api.example.com".to_string());
let k = api_key.clone();
let u = base_url.clone();
let mut builder = ScriptedTool::builder("api");
builder = builder.tool_fn(
ToolDef::new("get_user", "Fetch user by ID"),
move |args: &ToolArgs| {
let _key = &*k; // shared API key
let _url = &*u; // shared base URL
Ok(format!("{{\"id\":1}}\n"))
},
);
let k2 = api_key.clone();
let u2 = base_url.clone();
builder = builder.tool_fn(
ToolDef::new("list_orders", "List orders"),
move |_args: &ToolArgs| {
let _key = &*k2;
let _url = &*u2;
Ok(format!("[]\n"))
},
);
let _tool = builder.build();For mutable shared state, use Arc<Mutex<T>>:
use bashkit::{ScriptedTool, ToolArgs, ToolDef};
use std::sync::{Arc, Mutex};
let call_count = Arc::new(Mutex::new(0u64));
let c = call_count.clone();
let tool = ScriptedTool::builder("api")
.tool_fn(
ToolDef::new("tracked", "Counted call"),
move |_args: &ToolArgs| {
let mut count = c.lock().unwrap();
*count += 1;
Ok(format!("call #{count}\n"))
},
)
.build();§State across execute() calls
Each execute() creates a fresh Bash interpreter — no state carries over.
This is a security feature (clean sandbox per call). The LLM carries state
between calls via its context window: it sees stdout from each call and can
pass relevant data from one call’s output into the next call’s script.
For persistent state across calls via callbacks, use Arc in closures —
the same Arc<ToolCallback> instances are reused across execute() calls.
Structs§
- Discover
Tool - A
Toolthat exposes onlydiscoverandhelpbuiltins for runtime schema discovery. Returned byScriptingToolSet::toolsinDiscoveryMode::WithDiscoverymode alongside the main script tool. - Scripted
Command Invocation - One builtin/tool invocation inside a scripted tool execute.
- Scripted
Execution Trace - Inner execution trace captured for the last
ScriptedTool::execute()call. - Scripted
Tool - A
Toolthat orchestrates multiple tools via bash scripts. - Scripted
Tool Builder - Builder for
ScriptedTool. - Scripting
Tool Set - Higher-level wrapper around
ScriptedToolwith mode-controlled tool exposure. - Scripting
Tool SetBuilder - Builder for
ScriptingToolSet. - Tool
Args - Parsed arguments passed to a tool exec function.
- ToolDef
- OpenAPI-style tool definition: name, description, input schema.
- Tool
DefExtension - Bash extension that registers ToolDef-backed commands plus
helpanddiscover. - Tool
DefExtension Builder - Builder for
ToolDefExtension. - Tool
Impl - Complete tool: definition + sync/async exec functions.
Enums§
- Callback
Kind - Sync or async callback for a registered tool.
- Discovery
Mode - Controls how
ScriptingToolSet::toolsexposes tool information to the LLM. - Scripted
Command Kind - Kind of inner command invocation recorded during a
ScriptedToolexecute.
Type Aliases§
- Async
Tool Callback - Alias for
AsyncToolExec(backward compatibility). - Async
Tool Exec - Asynchronous execution function for a tool.
- Sync
Tool Exec - Synchronous execution function for a tool.
- Tool
Callback - Alias for
SyncToolExec(backward compatibility).