Vercel Logo

Create the Bash Tool

Since you're building a file system agent, you need to instruct the agent on how to use bash to navigate. You'll do this by defining a new tool in lib/tools.ts, a function that returns a tool from the AI SDK.

Outcome

You have a createBashTool function in lib/tools.ts that accepts a Sandbox, defines a Zod schema for command and args, and executes commands via sandbox.runCommand.

Fast Track

  1. Create createBashTool in lib/tools.ts using the tool helper from ai
  2. Define a Zod input schema with command (string) and args (string array)
  3. Implement execute to call sandbox.runCommand and return stdout, stderr, and exitCode

Hands-on Exercise 1.3

Build the complete bash tool in lib/tools.ts.

Requirements:

  1. Import tool from ai, z from zod, and the Sandbox type from @vercel/sandbox
  2. Export a createBashTool function that accepts a Sandbox parameter
  3. Return a tool() with a description, Zod input schema, and execute function
  4. The execute function runs the command in the sandbox and returns stdout, stderr, and exitCode

Implementation hints:

  • Via a Zod schema, you are informing the agent that it needs to generate a command and args to pass to this tool
  • Use .describe() on each Zod field to give the LLM context about what to put there
  • sandbox.runCommand(command, args) returns a result object. Call .stdout() and .stderr() on it (they're async).
  • Import Sandbox as a type-only import since you only need it for the function signature

Creating the tool

Start by defining the tool without sandbox execution. This function returns a tool to execute bash commands to explore files, but doesn't actually execute any code yet:

lib/tools.ts
import { tool } from 'ai';
import { z } from 'zod';
 
export function createBashTool() {
  return tool({
    description: `
      Execute bash commands to explore transcript and instruction files.
      Examples (not exhaustive): ls, cat, less, head, tail, grep
      `,
    inputSchema: z.object({
      command: z.string().describe('The bash command to execute'),
      args: z.array(z.string()).describe('Arguments to pass to the command')
    }),
    execute: async ({ command, args }) => {
      // code that executes when the tool is called
    }
  });
}

As it stands, the execute callback function is empty. To actually execute the bash that the LLM is generating, you need to give it a safe execution environment. This is where sandbox comes in.

To allow a sandbox instance to be passed in, update the function declaration so that it expects a sandbox as a parameter:

lib/tools.ts
import type { Sandbox } from '@vercel/sandbox';
 
export function createBashTool(sandbox: Sandbox) {
  // ...
}

Define what the tool does

Pass the commands and args to the sandbox to execute:

lib/tools.ts
execute: async ({ command, args }) => {
  const result = await sandbox.runCommand(command, args);
  const textResults = await result.stdout();
  const stderr = await result.stderr();
  return {
    stdout: textResults,
    stderr: stderr,
    exitCode: result.exitCode,
  };
},

Using the runCommand method on sandbox, you run the generated command and await the standard output, error output, and exit status of the process.

Now you're almost ready to give this tool to the agent to use. But first, you need to initialize the sandbox in the agent so you can pass it to the createBashTool function.

Try It

This tool isn't connected to the agent yet. You'll do that in the next lesson. For now, verify the file compiles:

  1. Check for TypeScript errors in your editor. If lib/tools.ts shows no red squiggles, the types are correct.

  2. Verify the export. createBashTool should be a named export that takes a Sandbox and returns a tool.

No runtime test yet

You can't test the tool in the browser until you wire it up to the agent in the next lesson. For now, just verify it compiles.

Commit

git add lib/tools.ts
git commit -m "feat(tools): add createBashTool with Zod schema"

Done-When

  • lib/tools.ts exports createBashTool
  • The function accepts a Sandbox parameter and returns a tool()
  • The Zod schema defines command (string) and args (string array) with .describe() annotations
  • The execute function calls sandbox.runCommand and returns { stdout, stderr, exitCode }
  • No TypeScript errors in the file

Solution

lib/tools.ts
import { tool } from 'ai';
import { z } from 'zod';
import type { Sandbox } from '@vercel/sandbox';
 
export function createBashTool(sandbox: Sandbox) {
  return tool({
    description: `
      Execute bash commands to explore transcript and instruction files.
      Examples (not exhaustive): ls, cat, less, head, tail, grep
      `,
    inputSchema: z.object({
      command: z.string().describe('The bash command to execute'),
      args: z.array(z.string()).describe('Arguments to pass to the command')
    }),
    execute: async ({ command, args }) => {
      const result = await sandbox.runCommand(command, args);
      const textResults = await result.stdout();
      const stderr = await result.stderr();
      return {
        stdout: textResults,
        stderr: stderr,
        exitCode: result.exitCode
      };
    }
  });
}