Skip to content

Shell Module

The Shell interface gives your agent the ability to run shell commands to completion. v1 ships run only — interactive sessions (stdin, vim, REPLs) are reserved for a future spawn method.

Interface

typescript
interface Shell {
  run(cmd: string, opts?: RunOptions): Promise<RunResult>;
}

interface RunOptions {
  readonly cwd?: string;
  readonly env?: Record<string, string>;
  readonly signal?: AbortSignal;
  readonly timeoutMs?: number;
  /** Async callback — providers must await for backpressure. */
  readonly onStdout?: (chunk: Uint8Array) => Promise<void>;
  readonly onStderr?: (chunk: Uint8Array) => Promise<void>;
}

interface RunResult {
  readonly stdout: Uint8Array;
  readonly stderr: Uint8Array;
  readonly exitCode: number;
  readonly durationMs: number;
}

Real-time streaming

The onStdout / onStderr callbacks are how providers stream output as it arrives. The contract:

  • When callbacks ARE present: the provider streams chunks as they're produced and awaits each callback before continuing (backpressure).
  • When callbacks are NOT present: the provider may use a blocking exec call and return everything at once.

Whichever path is taken, result.stdout and result.stderr always contain the FULL accumulated output.

This dual-mode design lets providers like CloudflareSandboxShell switch between execStream (SSE) when callbacks are present and exec (blocking) when not — without callers needing to know which path is in play.

The auto-injected workspace__<name>__run tool always passes callbacks that emit chunks to the agent's event stream. So when an LLM calls workspace__box__run('npm install') in a real-time-capable provider, you get live progress in your agent stream.

signal.aborted semantics

Providers that support signal MUST break their iteration / kill the underlying process when signal.aborted flips to true. The result.exitCode after abort is provider-specific (typically 0 if no exit event was seen, or -1 if the process was killed before exit).

The CloudflareSandboxShell checks signal.aborted at iteration start in its streaming path — chunks in flight when abort fires are not accumulated and the callback is not invoked.

Auto-injected tool

For a workspace named <name> with shell: true:

ToolSchemaReturns
workspace__<name>__run{ command: string; cwd?: string; env?: Record<string, string>; timeoutMs?: number }{ stdout: string; stderr: string; exitCode: number; durationMs: number }

The tool emits workspace_stdout / workspace_stderr events to the agent's event stream as chunks arrive — your downstream consumers (the AI SDK frontend, custom event handlers) see live output.

Capability config

typescript
interface ShellCapConfig {
  /** Allowlist of command first-tokens. Other commands throw at the tool layer. */
  allowedCommands?: readonly string[];
  /** Default timeoutMs applied when the tool input doesn't override. */
  maxDurationMs?: number;
}

allowedCommands enforces a first-token allowlist:

typescript
capabilities: {
  shell: { allowedCommands: ['ls', 'cat', 'wc', 'grep'] },
}

The auto-injected tool checks command.split(/\s+/)[0] against the list before delegating to the provider. Useful for restricting an LLM to a small command vocabulary.

maxDurationMs becomes the default timeoutMs if the tool input doesn't supply one.

Deferred features

  • spawn — interactive sessions with stdin streaming, PTY support. Reserved for Pattern 3e v2.
  • stdin — passing data to a running command. Workaround: use writeFile to a temp path, then command < /tmp/path.
  • PTY — terminal emulation, color codes, vim/nano. Same v2 timeline as spawn.

Provider support matrix

Providershell supported
In-Memory
Local Bash✅ (subprocess)
Cloudflare Filestore
Cloudflare Sandbox✅ (with real-time streaming via execStream + SSE)

Source

Released under the MIT License.