This document describes the Node.js and Bun API surface available to Pi extensions running in the PiJS (QuickJS-based) runtime. Extension authors can use this matrix to determine whether their extension will run unmodified.
| Surface | Result | Details |
|---|---|---|
| Extension corpus (223 extensions) | 205/223 pass (91.9%) | 100% Tier 1, 95.4% Tier 2 |
| Scenario conformance | 24/25 pass (96.0%) | Registration, events, tools, session |
| Node API matrix | 13/13 pass (100%) | All critical Node builtins covered |
| Bun API matrix | 5/7 pass (71.4%) | connect/listen unsupported |
| Tier | Description | Corpus coverage | Policy |
|---|---|---|---|
| T1 | Simple single-file extensions | 38/38 (100%) | Run silently |
| T2 | Multi-registration (tools + events + flags) | 83/87 (95.4%) | Run silently |
| T3 | Multi-file with local imports | 79/90 (87.8%) | Run + warn if relative imports fail |
| T4 | Extensions with npm dependencies | 1/3 (33.3%) | Blocked unless virtual stub exists |
| T5 | Extensions using exec/network APIs | 4/5 (80.0%) | Capability-gated |
These modules are shimmed in PiJS and behave like their Node.js counterparts for the API subset that extensions actually use.
| Module | Key APIs | Test coverage | Notes |
|---|---|---|---|
node:path |
join, resolve, dirname, basename, extname, sep, posix, win32 |
Full | POSIX semantics |
node:fs |
readFileSync, writeFileSync, statSync, mkdirSync, readdirSync, unlinkSync, rmSync, copyFileSync, renameSync, appendFileSync, accessSync, existsSync, realpathSync |
33 tests | Rooted to extension dir by default |
node:fs/promises |
readFile, writeFile, stat, mkdir, readdir, unlink, rm, access, copyFile, rename |
Included above | Async versions of fs shim |
node:crypto |
createHash, createHmac, randomUUID, randomBytes, randomInt, timingSafeEqual, getHashes |
29 tests | SHA-256, SHA-512, SHA-1, MD5 |
node:buffer |
Buffer.from, Buffer.alloc, Buffer.concat, Buffer.isBuffer, Buffer.byteLength, .toString(), .slice(), .subarray(), .compare(), .equals(), .indexOf(), .copy() |
41 tests | Full Buffer protocol |
node:child_process |
spawnSync, execSync, execFileSync, spawn, exec, execFile |
53 tests | Capability-gated (exec) |
node:http |
request, get, createServer, STATUS_CODES, METHODS, Agent |
40 tests | createServer throws (sandbox) |
node:https |
request, get |
Shared with http | Same as node:http |
node:events |
EventEmitter, on, emit, once, removeListener, removeAllListeners, listenerCount |
26 tests | Full EventEmitter pattern |
node:os |
platform, hostname, tmpdir, homedir, cpus, arch, type, release, userInfo, EOL |
Included | Returns host values |
node:url |
URL, URLSearchParams, parse, format, resolve |
6 tests | WHATWG URL standard |
node:process |
env, argv, cwd, exit, platform, arch, version, pid, hrtime |
24 tests | exit is sandboxed |
node:util |
format, inspect, inherits, deprecate, debuglog, types, TextEncoder, TextDecoder, stripVTControlCharacters |
Included | Standard utility functions |
node:stream |
Readable, Writable, Transform, Duplex, PassThrough, pipeline, finished |
Included | Stream constructors + helpers |
node:stream/promises |
pipeline, finished |
Included | Promise-based stream helpers |
node:querystring |
parse, stringify, encode, decode |
Included | Query string utilities |
node:assert |
ok, strictEqual, deepStrictEqual, throws, rejects, fail |
Included | Test assertion helpers |
node:string_decoder |
StringDecoder |
Included | UTF-8 string decoding |
node:module |
createRequire |
Included | Module system compat |
These modules expose a subset of their Node.js API. Missing functions throw with a clear error message identifying the unsupported call.
| Module | Supported | Unsupported | Notes |
|---|---|---|---|
node:net |
createConnection (stub), Socket (stub) |
createServer |
Network listen blocked by sandbox |
node:readline |
createInterface (stub) |
Interactive mode | Non-interactive only |
These modules are blocked because they require capabilities outside the extension sandbox.
| Module | Reason | Alternative |
|---|---|---|
vm |
Arbitrary code execution | Use extension API directly |
worker_threads |
Thread creation | Single-threaded runtime |
cluster |
Process forking | Single-process runtime |
dgram |
Raw UDP sockets | Use pi.http() for network |
tls |
Raw TLS sockets | Use node:https instead |
Pi provides a targeted Bun compatibility surface via globalThis.Bun and
import "bun".
| API | Status | Notes |
|---|---|---|
Bun.argv |
Supported | Process arguments |
Bun.file(path) |
Supported | Returns object with exists(), text(), arrayBuffer(), json() |
Bun.write(path, data) |
Supported | Write files |
Bun.which(command) |
Supported | Locate executables on PATH |
Bun.spawn(...) |
Supported | Capability-gated (exec) |
Bun.connect(...) |
Unsupported | Use pi.http() or node:http instead |
Bun.listen(...) |
Unsupported | Server APIs are outside the sandbox |
Extensions that import popular npm packages get virtual stubs that expose the package's public API shape. These stubs allow extensions to load and register without runtime errors, even when the real package is not installed.
| Package | Key exports |
|---|---|
@mariozechner/pi-coding-agent |
ExtensionAPI, Tool, SlashCommand, EventHook |
@mariozechner/pi-ai |
AI, Message, StreamEvent |
@mariozechner/pi-tui |
TUI, Widget, Layout |
@sinclair/typebox |
Type, Static, TSchema |
| Package | Key exports |
|---|---|
@modelcontextprotocol/sdk/* |
MCP client/server/transport types |
vscode-languageserver-protocol/* |
LSP types and protocol definitions |
jsonwebtoken |
sign, verify, decode |
uuid |
v4, v5, v7, NIL |
dotenv |
config, parse |
shell-quote |
parse, quote |
ms |
Duration parsing |
diff |
diffChars, diffLines, createPatch |
glob |
glob, globSync |
| Package | Key exports |
|---|---|
node-pty |
spawn (returns PTY stub) |
chokidar |
watch (returns watcher stub) |
@xterm/headless |
Terminal |
@xterm/addon-serialize |
SerializeAddon |
turndown |
TurndownService |
turndown-plugin-gfm |
gfm, tables, strikethrough |
@mozilla/readability |
Readability, isProbablyReaderable |
beautiful-mermaid |
render |
jsdom |
JSDOM |
| Package | Key exports |
|---|---|
@opentelemetry/api |
trace, context, propagation, SpanStatusCode |
@opentelemetry/sdk-trace-base |
BasicTracerProvider, SimpleSpanProcessor |
@opentelemetry/resources |
Resource |
@opentelemetry/exporter-trace-otlp-http |
OTLPTraceExporter |
@opentelemetry/semantic-conventions |
SEMRESATTRS_* constants |
The core Pi extension API is fully supported. This is the primary API that extensions use.
export default function activate(pi) {
// Register tools
pi.tool({ name: "my-tool", description: "...", schema: {}, run: async (input) => { ... } });
// Register slash commands
pi.slashCommand({ name: "/my-cmd", description: "...", run: async (args) => { ... } });
// Register event hooks
pi.on("onMessage", async (event) => { ... });
pi.on("onToolResult", async (event) => { ... });
// Register flags
pi.flag({ name: "my-flag", description: "...", default: false });
// Register shortcuts
pi.shortcut({ name: "my-shortcut", key: "ctrl+k", run: async () => { ... } });
// Register providers
pi.registerProvider({ name: "my-provider", models: [...], streamSimple: async (model, context) => { ... } });
}| API | Description |
|---|---|
pi.session.getState() |
Get current session state |
pi.session.getMessages() |
Get conversation messages |
pi.session.getName() / setName() |
Session name |
pi.session.getModel() / setModel() |
Active model |
pi.session.setLabel(key, value) |
Set status line labels |
pi.session.getThinkingLevel() / setThinkingLevel() |
Thinking mode |
pi.events(op, payload) |
Dispatch lifecycle events |
| API | Description |
|---|---|
pi.tool(name, input) |
Call built-in tools (read/write/edit/bash/grep/glob/ls) |
pi.exec(command, args, options) |
Run commands (capability-gated) |
pi.http(request) |
HTTP client (policy-controlled) |
pi.log({level, event, message}) |
Structured logging |
Extension access to sensitive APIs is governed by capability policies.
| Policy | exec |
http |
fs (outside root) |
env |
|---|---|---|---|---|
safe |
Denied | Denied | Denied | Denied |
balanced (default) |
Allowed | Allowed | Warned | Allowed |
permissive |
Allowed | Allowed | Allowed | Allowed |
Use pi doctor <path> --policy <profile> to check extension compatibility
under a specific policy.
The pi doctor command performs static analysis on extensions before loading:
# Text output (default)
pi doctor /path/to/extension
# JSON for automation
pi doctor /path/to/extension --format json
# Markdown for documentation
pi doctor /path/to/extension --format markdown
# Check against specific policy
pi doctor /path/to/extension --policy safeThe report includes:
- Verdict: PASS / WARN / FAIL
- Confidence score: 0-100 numeric rating
- Risk banner: Human-readable summary
- Findings: Per-issue category, severity, message, and line number
- Bare package specifiers (
import foo from "some-package") require a virtual stub entry. Extensions using unlisted npm packages will fail to load. - Relative imports (
import ./utils) work within bundled extensions but may fail for multi-file extensions that were not bundled. - Network imports (
import "https://...") are rejected.
- Single-threaded: No
worker_threads, no parallel execution. - No native addons: C/C++ Node addons cannot be loaded. Use hostcalls or WASM instead.
- Sandbox boundary:
createServer,listen, and other server-side APIs are blocked. Extensions are clients, not servers. - Filesystem scope: By default, filesystem access is scoped to the extension directory. Reads outside require explicit capability grants.
| Category | Count | Root cause |
|---|---|---|
| Multi-file relative specifiers | 4 | Unbundled multi-file extensions |
| Package module specifiers | 5 | npm packages without virtual stubs |
| Host-read policy denials | 4 | Read access outside extension root |
| Runtime shape/load errors | 4 | Extension structure mismatch |
| Test fixture artifacts | 1 | Not a real extension |
# Check your extension
pi doctor /path/to/your-extension
# Check with strict policy
pi doctor /path/to/your-extension --policy safe
# View supported policy modes
pi --explain-extension-policy# JSON output for automated checks
pi doctor /path/to/extension --format json | jq '.verdict'
# Exit code is always 0 (verdict is in output, not exit code)
# Parse the JSON verdict field for pass/fail decisionsIf your extension fails to load and you believe it should be compatible:
- Run
pi doctor /path/to/extension --format jsonand capture the output. - Check the findings array for specific error messages.
- If the failure is a missing module stub or shim gap, it may be eligible for a fix in the PiJS runtime.