# WASM Tools

URL: https://cogitator.app/docs/tools/wasm-tools

> Run tools in isolated WebAssembly sandboxes with Extism for security, speed, and portability.

## Overview

The `@cogitator-ai/wasm-tools` package provides tools that execute inside WebAssembly sandboxes powered by [Extism](https://extism.org/). Compared to Docker-based sandboxing, WASM tools offer:

* **100-500x faster cold start** than Docker containers
* **\~20x lower memory footprint** per tool instance
* **Memory-safe execution** in an isolated linear memory space
* **No network or filesystem access** -- no hosts are allowed and no directories are pre-opened

```typescript
import { Agent } from '@cogitator-ai/core';
import { createCalcTool, createHashTool, createCsvTool } from '@cogitator-ai/wasm-tools';

const agent = new Agent({
  name: 'data-processor',
  model: 'openai/gpt-6.1-sol',
  instructions: 'You process and verify data.',
  tools: [createCalcTool(), createHashTool(), createCsvTool()],
});
```

***

## Pre-built WASM Tools

The package ships with **14 ready-to-use** WASM tool factories. Each returns a `Tool` instance backed by a compiled `.wasm` module.

| Factory                   | Tool name      | Parameters                                                                                                                                                  | Description                                         |
| ------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| `createCalcTool()`        | `calculate`    | `expression`                                                                                                                                                | Evaluate math expressions (+, -, \*, /, %, parens)  |
| `createJsonTool()`        | `process_json` | `json`, `query?` (JSONPath)                                                                                                                                 | Parse JSON and query with JSONPath                  |
| `createHashTool()`        | `hash_text`    | `text`, `algorithm` (`sha256` \| `sha1` \| `md5`)                                                                                                           | Cryptographic hashes                                |
| `createBase64Tool()`      | `base64`       | `text`, `operation` (`encode` \| `decode`), `urlSafe?`                                                                                                      | Encode/decode Base64 (standard and URL-safe)        |
| `createSlugTool()`        | `slug`         | `text`, `separator?` (`-`), `lowercase?` (true), `maxLength?`                                                                                               | Convert text to URL-safe slugs with transliteration |
| `createValidationTool()`  | `validate`     | `value`, `type` (`email` \| `url` \| `uuid` \| `ipv4` \| `ipv6`)                                                                                            | Validate common formats                             |
| `createDiffTool()`        | `diff`         | `original`, `modified`, `format?` (`unified` \| `inline` \| `json`), `context?` (3)                                                                         | Generate diffs between texts (Myers algorithm)      |
| `createRegexTool()`       | `regex`        | `text`, `pattern`, `operation` (`match` \| `matchAll` \| `test` \| `replace` \| `split`), `flags?`, `replacement?`, `limit?`                                | Regex operations with ReDoS protection              |
| `createCsvTool()`         | `csv`          | `data` (string or rows), `operation` (`parse` \| `stringify`), `delimiter?`, `quote?`, `headers?`                                                           | Parse/generate CSV data (RFC 4180)                  |
| `createMarkdownTool()`    | `markdown`     | `markdown`, `options?: { sanitize?, gfm? }`                                                                                                                 | Convert Markdown to HTML (GFM subset)               |
| `createXmlTool()`         | `xml`          | `xml`, `query?` (XPath-like)                                                                                                                                | Parse XML to JSON with XPath-like queries           |
| `createDatetimeTool()`    | `datetime`     | `operation` (`parse` \| `format` \| `add` \| `subtract` \| `diff` \| `now`), `date?`, `format?`, `timezone?`, `amount?`, `unit?`, `endDate?`                | Parse, format, manipulate dates (UTC + offsets)     |
| `createCompressionTool()` | `compression`  | `data`, `operation` (`compress` \| `decompress`), `inputEncoding?`, `outputEncoding?`, `level?` (0-9, 6)                                                    | Gzip compress/decompress data                       |
| `createSigningTool()`     | `signing`      | `operation` (`generateKeypair` \| `sign` \| `verify`), `algorithm` (`ed25519`), `message?`, `privateKey?`, `publicKey?`, `signature?`, `encoding?`, `seed?` | Ed25519 keypair generation, sign, and verify        |

Every factory accepts an optional `{ timeout }` in milliseconds — 5000 by default, 10000 for `compression` and `signing`:

```typescript
const calc = createCalcTool({ timeout: 10000 });
```

The Zod schemas are exported too (`calcToolSchema`, `jsonToolSchema`, …, `signingToolSchema`, with matching `CalcToolInput`-style types), as is `getWasmPath(name)`, the absolute path of a bundled module (`getWasmPath('calc')`). With `@cogitator-ai/sandbox` the package path `@cogitator-ai/wasm-tools/wasm/calc.wasm` works as well: the executor resolves it from your application. `calcToolConfig`, `jsonToolConfig`, `hashToolConfig` and `base64ToolConfig` are ready-made `SandboxConfig`s for those four modules.

### Executing Tools

Inside Cogitator, tools run through the `@cogitator-ai/sandbox` WASM executor; only when that package is not installed or cannot start does the runtime call the tool's own `execute()`. A WASM call never falls back to Docker or the host. You can also call them directly -- parameters are validated with the Zod schema, the module runs in an Extism worker thread, the timeout terminates the worker, and `context.signal` aborts the call:

```typescript
const result = await createCalcTool().execute(
  { expression: '(2 + 3) * 4' },
  { agentId: 'a', runId: 'r', signal: AbortSignal.timeout(5000) }
);
// { result: 20, expression: '(2 + 3) * 4' }
```

Plugin-level errors come back as data (`{ ..., error }`) so the agent can recover. A few behaviours worth knowing:

* **json**: JSONPath subset -- `.key`, `['key']`, `[n]` (negative allowed), slices, `*`, `..key`. Multi-value paths return arrays; missing paths return `found: false`.
* **xml**: `/root/child` (absolute from the root element), `//name`, `*`, `name[2]`, `/@attr`, `/text()`. A query without matches returns `type: 'empty'`. Malformed XML is rejected; DTD entities are never expanded.
* **diff**: `unified` (default) is a standard unified diff with `---`/`+++` headers and `@@` hunks; `inline` and `json` are also available.
* **validation**: IPv4 with leading zeros is rejected, IPv6 is normalized to the RFC 5952 canonical form, and only the email domain is lower-cased.
* **markdown**: `sanitize` defaults to `true` -- raw HTML is escaped and `javascript:`/`data:`-style URLs become `#`.
* **compression**: gzip interoperable with zlib; `decompress` defaults to base64 in / UTF-8 out and verifies CRC32.
* **signing**: WASM has no secure RNG, so a 32-byte seed is generated on the host for `generateKeypair` (the seed is the private key). Signatures interoperate with `node:crypto`.
* **datetime**: month arithmetic clamps to month end (`Jan 31 + 1 month = Feb 29`), impossible dates are rejected.
* **regex**: nested unbounded quantifiers are rejected; any other catastrophic pattern is stopped by the timeout.

***

## Defining Custom WASM Tools

Use `defineWasmTool()` to wrap any `.wasm` module as a Cogitator tool:

```typescript
import { defineWasmTool } from '@cogitator-ai/wasm-tools';
import { z } from 'zod';

const imageProcessor = defineWasmTool({
  name: 'process_image',
  description: 'Resize and crop images in a WASM sandbox',
  wasmModule: './plugins/image-proc.wasm',
  wasmFunction: 'process',
  parameters: z.object({
    imageData: z.string().describe('Base64-encoded image'),
    operation: z.enum(['resize', 'crop', 'rotate']),
    width: z.number().optional(),
    height: z.number().optional(),
  }),
  category: 'utility',
  tags: ['image', 'processing'],
  timeout: 10000,
  wasi: true,
});
```

Modules compiled with `extism-js` (like the pre-built ones) embed QuickJS and need `wasi: true`. Extism `>= 2.0.0-rc13` is required for their host functions.

### WasmToolConfig

| Field          | Type           | Description                                |
| -------------- | -------------- | ------------------------------------------ |
| `name`         | `string`       | Tool name for LLM function calling         |
| `description`  | `string`       | What the tool does (shown to the LLM)      |
| `wasmModule`   | `string`       | Path or URL to the `.wasm` file            |
| `wasmFunction` | `string`       | Exported function to call (default: `run`) |
| `parameters`   | `ZodType`      | Zod schema for input validation            |
| `category`     | `ToolCategory` | Optional category for grouping             |
| `tags`         | `string[]`     | Optional tags for discovery                |
| `timeout`      | `number`       | Execution timeout in ms (default: 5000)    |
| `wasi`         | `boolean`      | Enable WASI (needed for extism-js modules) |

***

## WasmToolManager

For dynamic loading, hot-reloading, and managing multiple WASM modules at runtime, use `WasmToolManager`:

```typescript
import { WasmToolManager } from '@cogitator-ai/wasm-tools';

const manager = new WasmToolManager({ debounceMs: 100, useWasi: true, timeout: 30000 });

const tool = await manager.load('./plugins/calc.wasm');

const agent = new Agent({
  name: 'dynamic-agent',
  model: 'openai/gpt-6.1-sol',
  instructions: 'Use the loaded plugins.',
  tools: manager.getTools(),
});
```

| Option       | Default | Description                                    |
| ------------ | ------- | ---------------------------------------------- |
| `debounceMs` | `100`   | Debounce for file change events while watching |
| `useWasi`    | `false` | Instantiate plugins with WASI                  |
| `timeout`    | `30000` | Per-call timeout in ms                         |

A manager tool is named after the file (`calc.wasm` → `calc`), described as `WASM tool: calc`, and takes a free-form object: whatever the model passes is JSON-serialized and handed to the module's `run` export, and the output is parsed as JSON when possible. Loading a second file with the same base name replaces the first. Use `defineWasmTool()` when the model needs a real schema.

### File Watching

The manager can watch a glob pattern for `.wasm` files and automatically load, reload, or unload modules as they change on disk:

```typescript
await manager.watch('./plugins/*.wasm', {
  onLoad: (name, path) => console.log(`Loaded: ${name}`),
  onReload: (name, path) => console.log(`Reloaded: ${name}`),
  onUnload: (name, path) => console.log(`Unloaded: ${name}`),
  onError: (name, path, error) => console.error(`Error: ${name}`, error),
});
```

When a `.wasm` file is modified, the manager closes the old plugin instance and loads the new one; deleting the file unloads it. Agents keep the same tool object, so a reload is picked up on the next call. A manager watches one pattern at a time — `watch()` throws if it is already watching. `WasmLoader` and `FileWatcher`, the building blocks of the manager, are exported as well.

Plugins run in worker threads: when a call exceeds `timeout` (default 30 s) or its `context.signal` aborts, the call rejects and the plugin is replaced with a fresh instance.

### Manager API

```typescript
const tool = await manager.load('./path/to/module.wasm');

const allTools = manager.getTools();

const specific = manager.getTool('calc');

const mod = manager.getModule('calc');
// { name, path, plugin, tool, loadedAt }

const modules = manager.getModules();

await manager.close();
```

***

## How WASM Sandboxing Works

Each WASM tool runs inside an Extism plugin -- a lightweight WebAssembly runtime with strict isolation:

1. **Loading**: The `.wasm` binary is read from disk (compiled once and cached until the file changes) or fetched from a URL, and instantiated via the Extism SDK in a worker thread
2. **Execution**: Validated parameters are JSON-serialized and passed to the exported WASM function via `plugin.call(functionName, input)`
3. **Output**: The WASM function returns a byte buffer, which is parsed as JSON (or returned as raw text if parsing fails)
4. **Isolation**: Each plugin has its own linear memory space -- it cannot access host memory, and with no pre-opened directories or allowed hosts it has no file system or network access even with WASI enabled

```
┌─────────────────────────────────────┐
│          Host Process (Node.js)      │
│                                      │
│  params ──► JSON.stringify ──────┐   │
│                                  │   │
│  ┌───────────────────────────────▼─┐ │
│  │        Extism WASM Sandbox      │ │
│  │                                 │ │
│  │  ┌─────────────────────────┐    │ │
│  │  │   Own linear memory     │    │ │
│  │  │   No host access        │    │ │
│  │  │   No network            │    │ │
│  │  │   No filesystem         │    │ │
│  │  └─────────────────────────┘    │ │
│  │                                 │ │
│  └───────────────────────────────┬─┘ │
│                                  │   │
│  result ◄── JSON.parse ◄────────┘   │
└─────────────────────────────────────┘
```

### Security Benefits

* **No access to the host process** -- the module only sees the JSON input it is given
* **Memory bounds checking** -- buffer overflows in WASM trap instead of corrupting host memory
* **No ambient authority** -- no file system, network, or secure randomness inside the sandbox (randomness, when needed, is supplied by the host)
* **Timeout enforcement** -- the worker thread running a long computation is terminated after the configured timeout
* **ReDoS containment** -- catastrophic regexes run in the worker, never on the host event loop; the timeout kills them

***

## WASM vs Docker Sandboxing

| Aspect          | WASM (Extism)       | Docker                   |
| --------------- | ------------------- | ------------------------ |
| Cold start      | \< 5 ms             | 500 ms - 2 s             |
| Memory overhead | \~2 MB per instance | \~50 MB per container    |
| Isolation       | Linear memory       | Full OS-level            |
| Network access  | None                | Configurable             |
| File system     | None                | Mountable volumes        |
| Best for        | Pure computation    | I/O-heavy, multi-process |

Use WASM tools when you need fast, lightweight sandboxing for pure computation (hashing, parsing, math, text processing). Use Docker sandboxing when tools need file system access, network calls, or a full OS environment.
