@stdiobus/node
v2.1.3
Published
Native Node.js binding for stdio_bus — AI agent transport layer. Prebuild binaries, Docker fallback.
Maintainers
Readme
Installation
npm install @stdiobus/nodePrebuilt native binaries are included. No C compiler or build tools required.
Requirements:
- Node.js >= 18.0.0
- macOS (x64, arm64) or Linux (x64, arm64) for native backend
- Docker (optional) — required only on Windows or unsupported platforms
Quick Start
const { StdioBus } = require('@stdiobus/node');
const bus = new StdioBus({
config: {
pools: [{
id: 'echo',
command: 'node',
args: ['./workers/echo-worker.js'],
instances: 1,
}],
},
});
await bus.start();
const result = await bus.request('echo', { message: 'hello' });
console.log(result);
await bus.stop();
bus.destroy();TypeScript
The package ships with full type declarations.
import { StdioBus, BusState } from '@stdiobus/node';
import type { StdioBusOptions, BusStats } from '@stdiobus/node';
const options: StdioBusOptions = {
config: {
pools: [{
id: 'worker',
command: 'node',
args: ['./worker.js'],
instances: 2,
}],
limits: {
max_input_buffer: 1_048_576,
max_output_queue: 4_194_304,
max_restarts: 5,
restart_window_sec: 60,
},
},
};
const bus = new StdioBus(options);
try {
await bus.start();
bus.onMessage((msg: string) => {
console.log('Received:', msg);
});
const result = await bus.request('tools/list', {}, { timeout: 10_000 });
console.log('Tools:', result);
} finally {
await bus.stop();
bus.destroy();
}Use Cases
ACP Agent
For typed streaming events, use
@stdiobus/agenticwithpromptStream()orprompt(). The example below shows the low-level transport approach.
const { StdioBus } = require('@stdiobus/node');
const bus = new StdioBus({
config: {
pools: [{
id: 'acp-worker',
command: 'node',
args: ['./workers/acp-worker.js'],
instances: 1,
}],
},
});
await bus.start();
const init = await bus.request('initialize', {
protocolVersion: 1,
clientInfo: { name: 'my-app', version: '1.0.0' },
clientCapabilities: {},
}, { timeout: 60_000 });
const session = await bus.request('session/new', {
cwd: process.cwd(),
mcpServers: [],
});
const result = await bus.request('session/prompt', {
sessionId: session.sessionId,
prompt: [{ type: 'text', text: 'What is 2+2?' }],
});
console.log('Response:', result);
await bus.stop();
bus.destroy();MCP Tools
const bus = new StdioBus({
config: {
pools: [{
id: 'mcp-tools',
command: 'node',
args: ['./workers/mcp-tools-worker.js'],
instances: 2,
}],
},
});
await bus.start();
const tools = await bus.request('tools/list');
const output = await bus.request('tools/call', {
name: 'search_docs',
arguments: { query: 'retry policy' },
});
console.log('Tools:', tools);
console.log('Output:', output);
await bus.stop();
bus.destroy();TCP Server
Accept external client connections over TCP:
const port = Number(process.env.PORT) || 8080;
const bus = new StdioBus({
config: {
pools: [{
id: 'worker',
command: 'node',
args: ['./worker.js'],
instances: 4,
}],
},
listenMode: 'tcp',
tcpHost: '0.0.0.0',
tcpPort: port,
});
await bus.start();
console.log(`Listening on TCP port ${port}`);
// Clients connect and send NDJSON: nc localhost 8080Docker Backend
On Windows or when native binaries are unavailable, the SDK runs stdio Bus inside a Docker container and communicates over TCP:
const bus = new StdioBus({
configPath: './config.json',
backend: 'docker',
docker: {
image: 'stdiobus/stdiobus:node20',
pullPolicy: 'if-missing',
startupTimeoutMs: 15_000,
},
});Set backend: 'auto' (default) to use native when available, Docker otherwise.
Platform Support
| Platform | Architecture | Native | Docker | |----------|-------------|--------|--------| | macOS | x64 | ✓ | ✓ | | macOS | arm64 | ✓ | ✓ | | Linux | x64 | ✓ | ✓ | | Linux | arm64 | ✓ | ✓ | | Windows | x64 | — | ✓ |
API Reference
Constructor
new StdioBus(options: StdioBusOptions)| Option | Type | Default | Description |
|--------|------|---------|-------------|
| configJson | object | — | Programmatic config (recommended) |
| configPath | string | — | Path to JSON config file |
| backend | 'auto' \| 'native' \| 'docker' | 'auto' | Backend selection |
| listenMode | 'none' \| 'tcp' \| 'unix' | 'none' | External listener mode |
| tcpHost | string | '127.0.0.1' | TCP bind address |
| tcpPort | number | — | TCP port (required for tcp mode) |
| unixPath | string | — | Unix socket path (required for unix mode) |
| logLevel | number | 1 | 0=DEBUG, 1=INFO, 2=WARN, 3=ERROR |
| pollIntervalMs | number | 10 | Native backend poll interval |
| docker | DockerOptions | — | Docker backend configuration |
configJson and configPath are mutually exclusive. One is required.
Lifecycle
| Method | Returns | Description |
|--------|---------|-------------|
| start() | Promise<void> | Start bus and spawn workers |
| stop(timeoutSec?) | Promise<void> | Graceful shutdown (default: 30s) |
| destroy() | void | Release all resources |
Messaging
| Method | Returns | Description |
|--------|---------|-------------|
| request(method, params?, options?) | Promise<T> | Send request, await response |
| send(message) | boolean | Send raw JSON-RPC string |
| onMessage(handler) | void | Register message handler |
State
| Method | Returns | Description |
|--------|---------|-------------|
| getState() | number | Bus state (0–4) |
| getStats() | BusStats | Runtime statistics |
| getWorkerCount() | number | Running workers |
| getClientCount() | number | Connected clients (TCP/Unix) |
| getBackendType() | 'native' \| 'docker' | Active backend |
| getListenMode() | string | Listen mode |
| isRunning() | boolean | Convenience check |
Constants
import { BusState, ListenMode, BackendMode } from '@stdiobus/node';
BusState.CREATED // 0
BusState.STARTING // 1
BusState.RUNNING // 2
BusState.STOPPING // 3
BusState.STOPPED // 4
ListenMode.NONE // 'none'
ListenMode.TCP // 'tcp'
ListenMode.UNIX // 'unix'
BackendMode.AUTO // 'auto'
BackendMode.NATIVE // 'native'
BackendMode.DOCKER // 'docker'Known Behavior
- Workers that crash beyond
max_restartswithinrestart_window_secare not restarted. stop()sends SIGTERM to workers and waits up totimeoutSecfor graceful exit.request()correlates responses by JSON-RPCid. Each request gets a unique ID.- In embedded mode (
listenMode: 'none'), messages flow throughsend()/onMessage(). - In TCP/Unix modes, external clients connect and send NDJSON directly.
configJsonis serialized to a temp file internally, cleaned up ondestroy().- Always call
destroy()afterstop()to release native resources and clean up temp files.
Development
npm install # install dependencies
npm run build # esbuild (JS) + tsc (declarations)
npm run typecheck # type-check without emit
npm run test:e2e # npm pack → install → verify on macOS + Docker Linux
npm run test:e2e:native # macOS only
npm run test:e2e:docker # Docker Linux onlyBuild output:
out/
dist/index.js # CJS bundle (esbuild, minified)
tsc/*.d.ts # Type declarations (tsc)Contributing
- Open an issue describing the change before submitting a PR.
- All PRs must include tests covering the change.
- Run
npm run typecheck && npm run test:e2ebefore submitting.
Security
To report a security vulnerability, email [email protected]. Do not open a public issue.
