Skip to content

Commit a0ecc84

Browse files
authored
fix(tool): align toolkit library contract (#592)
## Summary - align the Rust tool contract with the toolkit library spec - update Rust docs, examples, and JS/Python wrapper metadata paths for the new contract - refresh wrapper tests/docs to match the new help/system prompt surfaces ## Validation - `cargo fmt --check` - `cargo clippy --all-targets --all-features -- -D warnings` - `cargo test --all-features` (fails only in `bash_comparison_tests`, matching `origin/main` at `08d4a33` with the same 81 baseline mismatches) - `npm run build` - `npm test` - native Python wrapper check via temp venv: `maturin develop && pytest tests/test_bashkit.py`
1 parent 1101a1e commit a0ecc84

File tree

24 files changed

+1528
-823
lines changed

24 files changed

+1528
-823
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ schemars = "1"
8383

8484
# Logging/tracing
8585
tracing = "0.1"
86+
tower = { version = "0.5", features = ["util"] }
8687

8788
# Serial test execution
8889
serial_test = "3"

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,41 @@ async fn main() -> anyhow::Result<()> {
5353
}
5454
```
5555

56+
## LLM Tool Contract
57+
58+
`BashTool` follows the toolkit-library contract: builder for reusable config,
59+
immutable tool metadata for discovery, and single-use executions for each call.
60+
61+
```rust
62+
use bashkit::{BashTool, Tool};
63+
use futures::StreamExt;
64+
65+
# #[tokio::main]
66+
# async fn main() -> anyhow::Result<()> {
67+
let tool = BashTool::builder()
68+
.username("agent")
69+
.hostname("sandbox")
70+
.build();
71+
72+
println!("{}", tool.description());
73+
println!("{}", tool.system_prompt());
74+
75+
let execution = tool.execution(serde_json::json!({
76+
"commands": "printf 'hello\nworld\n'"
77+
}))?;
78+
let mut stream = execution.output_stream().expect("stream available");
79+
80+
let handle = tokio::spawn(async move { execution.execute().await });
81+
while let Some(chunk) = stream.next().await {
82+
println!("{}: {}", chunk.kind, chunk.data);
83+
}
84+
85+
let output = handle.await??;
86+
assert_eq!(output.result["stdout"], "hello\nworld\n");
87+
# Ok(())
88+
# }
89+
```
90+
5691
## Overview
5792

5893
<div align="center">
@@ -264,6 +299,8 @@ Python bindings with LangChain integration are available in [crates/bashkit-pyth
264299
from bashkit import BashTool
265300

266301
tool = BashTool()
302+
print(tool.description())
303+
print(tool.help())
267304
result = await tool.execute("echo 'Hello, World!'")
268305
print(result.stdout)
269306
```

crates/bashkit-eval/src/agent.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ pub async fn run_agent_loop(
7777
.build();
7878
let tool_def = ToolDefinition {
7979
name: "bash".to_string(),
80-
description: tool.description(),
80+
description: tool.description().to_string(),
8181
input_schema: tool.input_schema(),
8282
};
8383

crates/bashkit-eval/src/scripting_agent.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,11 @@ pub async fn run_scripted_agent(
138138
if task.discovery_mode {
139139
builder = builder.with_discovery();
140140
}
141-
let mut tool: Box<dyn Tool> = Box::new(builder.build());
141+
let tool: Box<dyn Tool> = Box::new(builder.build());
142142

143143
let tool_def = ToolDefinition {
144144
name: tool.name().to_string(),
145-
description: tool.description(),
145+
description: tool.description().to_string(),
146146
input_schema: tool.input_schema(),
147147
};
148148

crates/bashkit-js/README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ console.log(result.stdout); // Hello, World!\n
2222
bash.executeSync('X=42');
2323
bash.executeSync('echo $X'); // stdout: 42\n
2424

25-
// With LLM tool metadata
25+
// With tool-contract metadata
2626
const tool = new BashTool();
2727
console.log(tool.name); // "bashkit"
2828
console.log(tool.inputSchema()); // JSON schema for LLM tool-use
29-
console.log(tool.systemPrompt()); // System prompt for LLMs
29+
console.log(tool.description()); // Token-efficient tool description
30+
console.log(tool.help()); // Markdown help document
31+
console.log(tool.systemPrompt()); // Compact system prompt
3032

3133
const r = tool.executeSync('echo hello');
3234
console.log(r.stdout); // hello\n
@@ -45,15 +47,15 @@ Core interpreter with virtual filesystem.
4547

4648
### `BashTool`
4749

48-
Interpreter + LLM tool metadata.
50+
Interpreter + tool-contract metadata.
4951

5052
- All `Bash` methods, plus:
5153
- `name` — tool name (`"bashkit"`)
5254
- `version` — version string
5355
- `shortDescription` — one-liner
54-
- `description()`full description
55-
- `help()` — help text
56-
- `systemPrompt()` — system prompt for LLMs
56+
- `description()`token-efficient tool description
57+
- `help()`Markdown help document
58+
- `systemPrompt()`compact system prompt for LLM orchestration
5759
- `inputSchema()` — JSON input schema
5860
- `outputSchema()` — JSON output schema
5961

crates/bashkit-js/__test__/tool-metadata.spec.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,13 @@ test("BashTool: metadata unchanged after reset", (t) => {
141141
t.is(tool.description(), descBefore);
142142
});
143143

144+
test("BashTool: systemPrompt reflects configured home path", (t) => {
145+
const tool = new BashTool({ username: "agent", hostname: "sandbox" });
146+
const prompt = tool.systemPrompt();
147+
t.true(prompt.includes("agent"));
148+
t.true(prompt.includes("/home/agent"));
149+
});
150+
144151
// ============================================================================
145152
// BashTool — execution (basic coverage, deep tests in basic.spec.ts)
146153
// ============================================================================

crates/bashkit-js/src/lib.rs

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,11 @@ impl Bash {
136136
}
137137

138138
// ============================================================================
139-
// BashTool — interpreter + LLM tool metadata
139+
// BashTool — interpreter + tool-contract metadata
140140
// ============================================================================
141141

142-
/// Bash interpreter with LLM tool metadata (schema, description, system_prompt).
142+
/// Bash interpreter with tool-contract metadata (`description`, `help`,
143+
/// `system_prompt`, schemas).
143144
///
144145
/// Use this when integrating with AI frameworks that need tool definitions.
145146
#[napi]
@@ -152,6 +153,29 @@ pub struct BashTool {
152153
max_loop_iterations: Option<u32>,
153154
}
154155

156+
impl BashTool {
157+
fn build_rust_tool(&self) -> RustBashTool {
158+
let mut builder = RustBashTool::builder();
159+
160+
if let Some(ref username) = self.username {
161+
builder = builder.username(username);
162+
}
163+
if let Some(ref hostname) = self.hostname {
164+
builder = builder.hostname(hostname);
165+
}
166+
167+
let mut limits = ExecutionLimits::new();
168+
if let Some(mc) = self.max_commands {
169+
limits = limits.max_commands(mc as usize);
170+
}
171+
if let Some(mli) = self.max_loop_iterations {
172+
limits = limits.max_loop_iterations(mli as usize);
173+
}
174+
175+
builder.limits(limits).build()
176+
}
177+
}
178+
155179
#[napi]
156180
impl BashTool {
157181
#[napi(constructor)]
@@ -238,44 +262,39 @@ impl BashTool {
238262
/// Get short description.
239263
#[napi(getter)]
240264
pub fn short_description(&self) -> &str {
241-
"Virtual bash interpreter with virtual filesystem"
265+
"Run bash commands in an isolated virtual filesystem"
242266
}
243267

244-
/// Get full description.
268+
/// Get token-efficient tool description.
245269
#[napi]
246270
pub fn description(&self) -> String {
247-
let tool = RustBashTool::default();
248-
tool.description()
271+
self.build_rust_tool().description().to_string()
249272
}
250273

251-
/// Get help text.
274+
/// Get help as a Markdown document.
252275
#[napi]
253276
pub fn help(&self) -> String {
254-
let tool = RustBashTool::default();
255-
tool.help()
277+
self.build_rust_tool().help()
256278
}
257279

258-
/// Get system prompt for LLMs.
280+
/// Get compact system-prompt text for orchestration.
259281
#[napi]
260282
pub fn system_prompt(&self) -> String {
261-
let tool = RustBashTool::default();
262-
tool.system_prompt()
283+
self.build_rust_tool().system_prompt()
263284
}
264285

265286
/// Get JSON input schema as string.
266287
#[napi]
267288
pub fn input_schema(&self) -> napi::Result<String> {
268-
let tool = RustBashTool::default();
269-
let schema = tool.input_schema();
289+
let schema = self.build_rust_tool().input_schema();
270290
serde_json::to_string_pretty(&schema)
271291
.map_err(|e| napi::Error::from_reason(format!("Schema serialization failed: {e}")))
272292
}
273293

274294
/// Get JSON output schema as string.
275295
#[napi]
276296
pub fn output_schema(&self) -> napi::Result<String> {
277-
let tool = RustBashTool::default();
278-
let schema = tool.output_schema();
297+
let schema = self.build_rust_tool().output_schema();
279298
serde_json::to_string_pretty(&schema)
280299
.map_err(|e| napi::Error::from_reason(format!("Schema serialization failed: {e}")))
281300
}

crates/bashkit-js/wrapper.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export class Bash {
8383
}
8484

8585
/**
86-
* Bash interpreter with LLM tool metadata (schema, description, system_prompt).
86+
* Bash interpreter with tool-contract metadata.
8787
*
8888
* Use this when integrating with AI frameworks that need tool definitions.
8989
*
@@ -94,6 +94,7 @@ export class Bash {
9494
* const tool = new BashTool();
9595
* console.log(tool.name); // "bashkit"
9696
* console.log(tool.inputSchema()); // JSON schema string
97+
* console.log(tool.help()); // Markdown help document
9798
*
9899
* const result = tool.executeSync('echo hello');
99100
* console.log(result.stdout); // hello\n
@@ -141,17 +142,17 @@ export class BashTool {
141142
return this.native.shortDescription;
142143
}
143144

144-
/** Full description. */
145+
/** Token-efficient tool description. */
145146
description(): string {
146147
return this.native.description();
147148
}
148149

149-
/** Help text. */
150+
/** Markdown help document. */
150151
help(): string {
151152
return this.native.help();
152153
}
153154

154-
/** System prompt for LLMs. */
155+
/** Compact system prompt for orchestration. */
155156
systemPrompt(): string {
156157
return this.native.systemPrompt();
157158
}

crates/bashkit-python/README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,16 @@ bash = Bash(
8080

8181
### BashTool — Convenience Wrapper for AI Agents
8282

83-
`BashTool` is a convenience wrapper specifically designed for AI agents. It wraps `Bash` and adds LLM tool metadata (schema, description, system prompt) needed by tool-use protocols. Use this when integrating with LangChain, PydanticAI, or similar agent frameworks.
83+
`BashTool` is a convenience wrapper specifically designed for AI agents. It wraps `Bash` and adds contract metadata (`description`, Markdown `help`, `system_prompt`, JSON schemas) needed by tool-use protocols. Use this when integrating with LangChain, PydanticAI, or similar agent frameworks.
8484

8585
```python
8686
from bashkit import BashTool
8787

8888
tool = BashTool()
8989
print(tool.input_schema()) # JSON schema for LLM tool-use
90+
print(tool.description()) # Token-efficient tool description
9091
print(tool.system_prompt()) # Token-efficient prompt
92+
print(tool.help()) # Markdown help document
9193

9294
result = await tool.execute("echo 'Hello!'")
9395
```
@@ -156,16 +158,16 @@ print(result.stdout) # Alice
156158
- `execute(commands: str) -> ExecResult` — execute commands asynchronously
157159
- `execute_sync(commands: str) -> ExecResult` — execute commands synchronously
158160
- `reset()` — reset interpreter state
159-
- `description() -> str` — tool description for LLM integration
160-
- `help() -> str` — detailed documentation
161-
- `input_schema() -> str` — JSON input schema
162-
- `output_schema() -> str` — JSON output schema
163161

164162
### BashTool
165163

166164
Convenience wrapper for AI agents. Inherits all execution methods from `Bash`, plus:
167165

166+
- `description() -> str` — token-efficient tool description
167+
- `help() -> str` — Markdown help document
168168
- `system_prompt() -> str` — token-efficient system prompt for LLM integration
169+
- `input_schema() -> str` — JSON input schema
170+
- `output_schema() -> str` — JSON output schema
169171

170172
### ExecResult
171173

0 commit comments

Comments
 (0)