Skip to content

Commit 25c0cbc

Browse files
cramforcequuu
andauthored
Prototype: Skills support for AI SDK (#6)
* Prototype: Skills support for AI SDK * pnpm-bs * correct path for skills * Finalize * Document * lint * build * build --------- Co-authored-by: Andrew Qu <qual1337@gmail.com>
1 parent a7d44f1 commit 25c0cbc

28 files changed

+1652
-81
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ jobs:
3434
- name: Type check
3535
run: pnpm typecheck
3636

37+
- name: Build
38+
run: pnpm build
39+
3740
- name: Test
3841
run: pnpm test:run
3942

40-
- name: Build
41-
run: pnpm build
43+

AGENTS.npm.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const result = await agent.generate({
4343
## Key Behaviors
4444

4545
1. **Default sandbox is just-bash** - Install `just-bash` or provide your own sandbox
46-
2. **Working directory defaults to `./workspace`** - All files written relative to `destination`
46+
2. **Working directory defaults to `/workspace`** - All files written relative to `destination`
4747
3. **Files are written before tools are returned** - Sandbox is pre-populated
4848
4. **Tool descriptions include file list** - LLM sees available files in bash tool description
4949
5. **No `stop()` method** - Sandbox lifecycle is managed externally
@@ -88,11 +88,11 @@ const { tools } = await createBashTool({ sandbox: existingSandbox });
8888
const { tools } = await createBashTool({
8989
onBeforeBashCall: ({ command }) => {
9090
console.log("Running:", command);
91-
// Return modified command or undefined to proceed unchanged
91+
return undefined; // Or return { command: modifiedCommand } to change it
9292
},
9393
onAfterBashCall: ({ command, result }) => {
9494
console.log(`Exit: ${result.exitCode}`);
95-
// Return modified result or undefined to proceed unchanged
95+
return undefined; // Or return { result: modifiedResult } to change it
9696
},
9797
});
9898
```

README.md

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,80 @@ const customSandbox: Sandbox = {
167167
// Your implementation here
168168
return "";
169169
},
170-
async writeFile(path, content) {
171-
// Your implementation here
170+
async writeFiles(files) {
171+
// Your implementation here - files is Array<{path, content}>
172172
},
173173
};
174174

175175
const { tools } = await createBashTool({ sandbox: customSandbox });
176176
```
177177

178+
## Skills (Experimental)
179+
180+
Skills are modular capabilities that extend agent functionality. Each skill is a directory containing a `SKILL.md` file with instructions and optional scripts.
181+
182+
```typescript
183+
import {
184+
experimental_createSkillTool as createSkillTool,
185+
createBashTool,
186+
} from "bash-tool";
187+
import { ToolLoopAgent } from "ai";
188+
189+
// Discover skills and get files to upload
190+
const { loadSkill, files, instructions } = await createSkillTool({
191+
skillsDirectory: "./skills",
192+
});
193+
194+
// Create bash tool with skill files
195+
const { tools } = await createBashTool({
196+
files,
197+
extraInstructions: instructions,
198+
});
199+
200+
// Use both tools with an agent
201+
const agent = new ToolLoopAgent({
202+
model,
203+
tools: { loadSkill, ...tools },
204+
});
205+
```
206+
207+
### Skill Directory Structure
208+
209+
```
210+
skills/
211+
├── csv/
212+
│ ├── SKILL.md # Required: instructions with YAML frontmatter
213+
│ ├── analyze.sh # Optional: scripts the agent can run
214+
│ └── filter.sh
215+
└── text/
216+
├── SKILL.md
217+
└── search.sh
218+
```
219+
220+
### SKILL.md Format
221+
222+
```markdown
223+
---
224+
name: csv
225+
description: Analyze and transform CSV files
226+
---
227+
228+
# CSV Processing
229+
230+
Use `./skills/csv/analyze.sh <file>` to analyze a CSV file.
231+
```
232+
233+
### How It Works
234+
235+
1. `createSkillTool` discovers skills and returns:
236+
- `loadSkill` - Tool for the agent to load a skill's instructions on demand
237+
- `files` - All skill files to pass to `createBashTool`
238+
- `instructions` - Extra instructions listing available skills
239+
240+
2. The agent sees skill names in the `loadSkill` tool description
241+
3. When the agent needs a skill, it calls `loadSkill("csv")` to get detailed instructions
242+
4. The agent uses `bash` to run scripts from `./skills/csv/`
243+
178244
## AI Agent Instructions
179245

180246
For AI agents working with bash-tool, additional guidance is available in `AGENTS.md`:

biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@
1212
"indentWidth": 2
1313
},
1414
"files": {
15-
"includes": ["src/**", "*.json", "*.ts"]
15+
"includes": ["src/**", "examples/**", "*.json", "*.ts"]
1616
}
1717
}

examples/skills-tool/README.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Skills Tool Example
2+
3+
This example demonstrates how to use `createSkillTool` with AI SDK's `ToolLoopAgent` to give an AI agent modular capabilities (skills) that it can discover and use on demand.
4+
5+
## Overview
6+
7+
The example includes two bash-based skills:
8+
9+
- **csv** - Analyze and transform CSV files using awk, cut, sort
10+
- **text** - Analyze and search text files using grep, sed, wc
11+
12+
## How It Works
13+
14+
1. `createSkillTool` discovers skills and returns their files
15+
2. Files are passed to `createBashTool` for sandbox upload
16+
3. The `ToolLoopAgent`:
17+
- Sees available skills in the `loadSkill` tool description
18+
- Calls `loadSkill("csv")` to get detailed instructions
19+
- Uses `bash` to run the skill's scripts
20+
- Loops until the task is complete
21+
22+
## Running the Example
23+
24+
```bash
25+
# From the repository root
26+
npx tsx examples/skills-tool/index.ts
27+
```
28+
29+
Requires `ANTHROPIC_API_KEY` environment variable.
30+
31+
## Code Overview
32+
33+
```typescript
34+
import { ToolLoopAgent } from "ai";
35+
import {
36+
experimental_createSkillTool as createSkillTool,
37+
createBashTool,
38+
} from "bash-tool";
39+
40+
// Discover skills and get files
41+
const { loadSkill, skills, files, instructions } = await createSkillTool({
42+
skillsDirectory: "./skills",
43+
});
44+
45+
// Create bash tool with skill files
46+
const { tools } = await createBashTool({
47+
files,
48+
extraInstructions: instructions,
49+
});
50+
51+
// Create agent with both tools
52+
const agent = new ToolLoopAgent({
53+
model: "anthropic/claude-haiku-4.5",
54+
tools: {
55+
loadSkill,
56+
bash: tools.bash,
57+
},
58+
});
59+
60+
// Run the agent
61+
const result = await agent.generate({
62+
prompt: "Analyze this CSV data...",
63+
});
64+
```
65+
66+
## Skill Structure
67+
68+
Each skill is a directory containing:
69+
70+
```
71+
skills/
72+
├── csv/
73+
│ ├── SKILL.md # Instructions (YAML frontmatter + markdown)
74+
│ ├── analyze.sh # Bash scripts
75+
│ ├── filter.sh
76+
│ ├── select.sh
77+
│ └── sort.sh
78+
└── text/
79+
├── SKILL.md
80+
├── stats.sh
81+
├── search.sh
82+
├── extract.sh
83+
└── wordfreq.sh
84+
```
85+
86+
## Creating Your Own Skills
87+
88+
1. Create a directory under `skills/`
89+
2. Add a `SKILL.md` with frontmatter:
90+
```yaml
91+
---
92+
name: my-skill
93+
description: What this skill does
94+
---
95+
96+
# Instructions for the AI
97+
98+
Explain how to use the scripts...
99+
```
100+
3. Add bash scripts that the AI can execute
101+
102+
## Key Concepts
103+
104+
- **Composable**: `createSkillTool` returns files, you control the sandbox via `createBashTool`
105+
- **ToolLoopAgent**: AI SDK's agent that automatically loops through tool calls until done
106+
- **Progressive disclosure**: The AI only sees skill names initially, loading full instructions on demand
107+
- **Bash-only**: Scripts use standard Unix tools (awk, sed, grep, sort, etc.)

examples/skills-tool/index.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* Example: Using createSkillTool with AI SDK ToolLoopAgent
3+
*
4+
* This example demonstrates how to create an AI agent with skills
5+
* that can process CSV and text files using bash tools.
6+
*
7+
* Run with: npx tsx examples/skills-tool/index.ts
8+
*/
9+
10+
import path from "node:path";
11+
import { ToolLoopAgent } from "ai";
12+
import {
13+
createBashTool,
14+
experimental_createSkillTool as createSkillTool,
15+
} from "../../src/index.js";
16+
17+
async function main() {
18+
// Discover skills and get files to upload
19+
const { loadSkill, skills, files, instructions } = await createSkillTool({
20+
skillsDirectory: path.join(import.meta.dirname, "skills"),
21+
});
22+
23+
console.log("Available skills:");
24+
for (const skill of skills) {
25+
console.log(` - ${skill.name}: ${skill.description}`);
26+
}
27+
console.log("");
28+
29+
// Create bash tool with skill files
30+
const { tools } = await createBashTool({
31+
files,
32+
extraInstructions: instructions,
33+
});
34+
35+
// Create the agent with skills
36+
const agent = new ToolLoopAgent({
37+
model: "anthropic/claude-haiku-4.5",
38+
tools: {
39+
loadSkill,
40+
bash: tools.bash,
41+
},
42+
instructions: `You are a data processing assistant with access to skills.
43+
Use loadSkill to discover how to use a skill, then use bash to run its scripts.
44+
Skills are located at ./skills/<skill-name>/.`,
45+
onStepFinish: ({ toolCalls, toolResults }) => {
46+
if (toolCalls && toolCalls.length > 0) {
47+
for (const call of toolCalls) {
48+
console.log(`Tool: ${call.toolName}`);
49+
if (call.toolName === "loadSkill" && "input" in call) {
50+
const input = call.input as { skillName: string };
51+
console.log(` Loading skill: ${input.skillName}`);
52+
} else if (call.toolName === "bash" && "input" in call) {
53+
const input = call.input as { command: string };
54+
console.log(` Command: ${input.command}`);
55+
}
56+
}
57+
}
58+
if (toolResults && toolResults.length > 0) {
59+
for (const result of toolResults) {
60+
if (result.toolName === "bash" && "output" in result) {
61+
const output = result.output as {
62+
stdout: string;
63+
exitCode: number;
64+
};
65+
if (output.stdout) {
66+
console.log(` Output:\n${output.stdout.slice(0, 500)}`);
67+
}
68+
}
69+
}
70+
console.log("");
71+
}
72+
},
73+
});
74+
75+
// Example prompt - the AI will discover and use skills as needed
76+
const prompt = `
77+
I have a CSV file with sales data. Here's the content:
78+
79+
date,product,quantity,price,region
80+
2024-01-15,Widget A,100,29.99,North
81+
2024-01-15,Widget B,50,49.99,South
82+
2024-01-16,Widget A,75,29.99,East
83+
2024-01-16,Widget C,200,19.99,North
84+
2024-01-17,Widget B,30,49.99,West
85+
2024-01-17,Widget A,150,29.99,North
86+
87+
Please:
88+
1. First, write this data to a file called sales.csv
89+
2. Use the csv skill to analyze the file
90+
3. Filter to show only North region sales
91+
4. Sort by quantity (highest first)
92+
`;
93+
94+
console.log("Sending prompt to agent...\n");
95+
96+
const result = await agent.generate({ prompt });
97+
98+
console.log("\n=== Final Response ===\n");
99+
console.log(result.text);
100+
101+
console.log("\n=== Agent Stats ===");
102+
console.log(`Steps: ${result.steps.length}`);
103+
console.log(`Total tokens: ${result.usage.totalTokens}`);
104+
}
105+
106+
main().catch(console.error);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
name: csv
3+
description: Analyze and transform CSV data using bash tools
4+
---
5+
6+
# CSV Processing Skill
7+
8+
Process CSV files using standard bash tools (awk, cut, sort, grep).
9+
10+
## Available Scripts
11+
12+
### analyze.sh
13+
Get statistics and summary of a CSV file.
14+
```bash
15+
bash /skills/csv/analyze.sh data.csv
16+
```
17+
18+
### filter.sh
19+
Filter rows where a column matches a value.
20+
```bash
21+
bash /skills/csv/filter.sh data.csv <column_number> <value>
22+
```
23+
24+
### select.sh
25+
Select specific columns from CSV.
26+
```bash
27+
bash /skills/csv/select.sh data.csv <col1,col2,col3>
28+
```
29+
30+
### sort.sh
31+
Sort CSV by a column.
32+
```bash
33+
bash /skills/csv/sort.sh data.csv <column_number> [--numeric] [--reverse]
34+
```
35+
36+
## Examples
37+
38+
```bash
39+
# Show CSV summary
40+
bash /skills/csv/analyze.sh sales.csv
41+
42+
# Filter where column 3 equals "active"
43+
bash /skills/csv/filter.sh users.csv 3 active
44+
45+
# Select columns 1, 2, and 4
46+
bash /skills/csv/select.sh data.csv 1,2,4
47+
48+
# Sort by column 2 numerically in reverse
49+
bash /skills/csv/sort.sh data.csv 2 --numeric --reverse
50+
```

0 commit comments

Comments
 (0)