Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ This source repository also contains examples that demonstrate how to integrate
- **[Claude Agent SDK](examples/claude-agent/research-assistant)** - Research assistant using Anthropic's Claude Agent SDK
- **[OpenAI Agents](examples/openai-agents/research-assistant)** - Research assistant using OpenAI Agents SDK
- **[Firecracker](examples/firecracker)** - Minimal Firecracker VM with AgentFS mounted via NFSv3
- **[AI SDK + just-bash](examples/ai-sdk-just-bash)** - Interactive AI agent using Vercel AI SDK with just-bash for command execution

See the **[examples](examples)** directory for more details.

Expand Down
3 changes: 3 additions & 0 deletions examples/ai-sdk-just-bash/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
dist/
.agentfs/
53 changes: 53 additions & 0 deletions examples/ai-sdk-just-bash/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# AI SDK + just-bash Code Explorer Agent

An interactive AI agent that combines [Vercel AI SDK](https://sdk.vercel.ai/), [just-bash](https://github.com/vercel-labs/just-bash) for bash command execution, and [AgentFS](https://github.com/tursodatabase/agentfs) for persistent filesystem storage.

This example is forked from the [just-bash bash-agent example](https://github.com/vercel-labs/just-bash/tree/main/examples/bash-agent) with AgentFS integration added.

## How It Works

- **Vercel AI SDK** - Orchestrates the AI agent with Claude as the model
- **just-bash** - Provides a bash tool that the AI can use to execute shell commands
- **AgentFS** - Backs the virtual filesystem with SQLite for persistence across sessions

## Files

- `main.ts` - Entry point
- `agent.ts` - Agent logic using AI SDK's `streamText` with just-bash's `createBashTool`
- `shell.ts` - Interactive readline shell

## Setup

1. Install dependencies:

```bash
npm install
```

2. Set your Anthropic API key:

```bash
export ANTHROPIC_API_KEY=your-key-here
```

3. Run:
```bash
npm start
```

## Usage

Ask questions like:

- "What commands are available?"
- "How is the grep command implemented?"
- "Show me the Bash class"
- "Find all test files"

Type `exit` to quit.

## Development

```bash
npm run typecheck
```
144 changes: 144 additions & 0 deletions examples/ai-sdk-just-bash/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/**
* AI agent with persistent AgentFS storage
*
* This file contains only the agent logic - see shell.ts for the interactive loop.
* Uses AgentFs to provide persistent filesystem storage backed by SQLite.
*
* The agent starts with the agentfs source code pre-loaded, so you can
* explore the agentfs codebase using agentfs itself!
*/

import * as nodeFs from "node:fs";
import * as path from "node:path";
import { glob } from "glob";
import { anthropic } from "@ai-sdk/anthropic";
import { streamText, stepCountIs } from "ai";
import { createBashTool } from "just-bash/ai";
import { AgentFS } from "agentfs-sdk";
import { agentfs } from "agentfs-sdk/just-bash";

export interface AgentRunner {
chat(
message: string,
callbacks: {
onText: (text: string) => void;
}
): Promise<void>;
}

export interface CreateAgentOptions {
onToolCall?: (command: string) => void;
onText?: (text: string) => void;
}

/**
* Creates an agent runner with persistent filesystem storage
*
* Uses AgentFs backed by SQLite - files persist across sessions.
*/
export async function createAgent(
options: CreateAgentOptions = {}
): Promise<AgentRunner> {
// Open AgentFS for persistent storage
const fs = agentfs(await AgentFS.open({ id: "just-bash-agent" }));

// Seed agentfs source files on first run
const agentfsRoot = path.resolve(import.meta.dirname, "../..");
if (!(await fs.exists("/README.md"))) {
console.log("Seeding AgentFS with agentfs source files...");

// Find all source files
const patterns = [
"**/*.ts",
"**/*.rs",
"**/*.toml",
"**/*.json",
"**/*.md",
];
const ignorePatterns = [
"**/node_modules/**",
"**/dist/**",
"**/target/**",
"**/.git/**",
"**/examples/just_bash/**",
];

// Collect all files first
const allFiles: string[] = [];
for (const pattern of patterns) {
const files = await glob(pattern, {
cwd: agentfsRoot,
ignore: ignorePatterns,
nodir: true,
});
allFiles.push(...files);
}

// Copy files with progress
let count = 0;
const total = allFiles.length;
for (const file of allFiles) {
const srcPath = path.join(agentfsRoot, file);
const destPath = "/" + file;

// Create parent directories
const dir = path.dirname(destPath);
if (dir !== "/") {
await fs.mkdir(dir, { recursive: true }).catch(() => {});
}

// Copy file
const content = nodeFs.readFileSync(srcPath, "utf-8");
await fs.writeFile(destPath, content);

count++;
if (count % 10 === 0 || count === total) {
process.stdout.write(`\rSeeding: ${count}/${total} files...`);
}
}
console.log("\nSeeded AgentFS with agentfs source files.");
}

const bashTool = createBashTool({
fs,
extraInstructions: `You are exploring the AgentFS codebase - a persistent filesystem for AI agents.
The filesystem is backed by AgentFS itself (SQLite), so files persist across sessions.

Use bash commands to explore:
- ls to see the project structure
- cat README.md to read documentation
- grep -r "pattern" . to search code
- find . -name "*.ts" to find TypeScript files

Key directories:
- /sdk/typescript/src - TypeScript SDK source
- /cli/src - CLI source (Rust)
- /integrations - Framework integrations`,
onCall: options.onToolCall,
});

const history: Array<{ role: "user" | "assistant"; content: string }> = [];

return {
async chat(message, callbacks) {
history.push({ role: "user", content: message });

let fullText = "";

const result = streamText({
model: anthropic("claude-sonnet-4-20250514"),
tools: { bash: bashTool },
stopWhen: stepCountIs(50),
messages: history,
});

for await (const chunk of result.textStream) {
options.onText?.(chunk);
callbacks.onText(chunk);
fullText += chunk;
}

history.push({ role: "assistant", content: fullText });
},
};
}
27 changes: 27 additions & 0 deletions examples/ai-sdk-just-bash/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env npx tsx
/**
* just-bash Agent with AgentFS
*
* Usage: npx tsx main.ts
*
* Requires ANTHROPIC_API_KEY environment variable.
*/

import { createAgent } from "./agent.js";
import { runShell } from "./shell.js";

let lastWasToolCall = false;

const agent = await createAgent({
onToolCall: (command) => {
const prefix = lastWasToolCall ? "" : "\n";
console.log(
`${prefix}\x1b[34m\x1b[1mExecuting bash tool:\x1b[0m \x1b[36m${command.trim()}\x1b[0m`
);
lastWasToolCall = true;
},
onText: () => {
lastWasToolCall = false;
},
});
runShell(agent);
17 changes: 17 additions & 0 deletions examples/ai-sdk-just-bash/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "just-bash-agentfs-example",
"version": "1.0.0",
"description": "Interactive AI agent with persistent AgentFS storage",
"type": "module",
"scripts": {
"start": "npx tsx main.ts",
"typecheck": "npx tsc --noEmit"
},
"dependencies": {
"@ai-sdk/anthropic": "^3.0.0",
"ai": "^6.0.0",
"agentfs-sdk": "file:../../sdk/typescript",
"glob": "^11.0.0",
"just-bash": "^1.0.1"
}
}
67 changes: 67 additions & 0 deletions examples/ai-sdk-just-bash/shell.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* Interactive shell for the just-bash agent with AgentFS
*/

import * as readline from "node:readline";
import type { AgentRunner } from "./agent.js";

const colors = {
reset: "\x1b[0m",
bold: "\x1b[1m",
dim: "\x1b[2m",
cyan: "\x1b[36m",
green: "\x1b[32m",
yellow: "\x1b[33m",
blue: "\x1b[34m",
};

export function runShell(agent: AgentRunner): void {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

console.log(`${colors.cyan}${colors.bold}╔══════════════════════════════════════════════════════════════╗
║ just-bash Agent with AgentFS ║
║ Persistent filesystem backed by SQLite ║
╚══════════════════════════════════════════════════════════════╝${colors.reset}
`);
console.log(
`${colors.dim}Type your question and press Enter. Type 'exit' to quit.${colors.reset}\n`
);

const prompt = (): void => {
rl.question(`${colors.green}You:${colors.reset} `, async (input) => {
const trimmed = input.trim();

if (!trimmed) {
prompt();
return;
}

if (trimmed.toLowerCase() === "exit") {
console.log("\nGoodbye!");
rl.close();
process.exit(0);
}

process.stdout.write(
`\n${colors.blue}${colors.bold}Agent:${colors.reset} `
);

try {
await agent.chat(trimmed, {
onText: (text) => process.stdout.write(text),
});
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(`\n${colors.yellow}Error: ${message}${colors.reset}`);
}

console.log("");
prompt();
});
};

prompt();
}
13 changes: 13 additions & 0 deletions examples/ai-sdk-just-bash/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"noEmit": true
},
"include": ["*.ts", "../../src/**/*.ts"],
"exclude": ["node_modules"]
}
29 changes: 29 additions & 0 deletions integrations/just-bash/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# AgentFs

Persistent filesystem backed by [AgentFS](https://github.com/tursodatabase/agentfs) (SQLite via Turso).

**Requires `[email protected]` or newer** (install with `npm install agentfs-sdk@next`).

## Usage

```typescript
import { AgentFS } from "agentfs-sdk";
import { AgentFs } from "just-bash";

const handle = await AgentFS.open({ path: ":memory:" }); // or file path
const fs = new AgentFs({ fs: handle });

await fs.writeFile("/hello.txt", "world");
const content = await fs.readFile("/hello.txt");

await handle.close();
```

## Notes

- Implements `IFileSystem` interface
- Directories created implicitly on write
- `chmod` is a no-op (AgentFS doesn't support modes)
- `symlink`/`readlink` use a JSON marker file (no native symlink support)
- `link` creates a copy (no native hard link support)
- `getAllPaths()` returns `[]` (no efficient way to enumerate)
Loading