Hello World WASM Plugin
A minimal example of a Bifrost plugin written in Go and compiled to WebAssembly using TinyGo.
Prerequisites
TinyGo Installation
TinyGo is required to compile Go code to WebAssembly with a small binary size.
macOS:
brew install tinygo
Linux (Ubuntu/Debian):
wget https://github.com/tinygo-org/tinygo/releases/download/v0.32.0/tinygo_0.32.0_amd64.deb
sudo dpkg -i tinygo_0.32.0_amd64.deb
Other platforms: See TinyGo Installation Guide
Building
# Build the WASM plugin
make build
# Build with size optimizations
make build-optimized
# Clean build artifacts
make clean
The compiled plugin will be at build/hello-world.wasm.
Plugin Structure
WASM plugins must export the following functions:
| Export | Signature | Description |
|---|---|---|
plugin_malloc |
(size: u32) -> u32 |
Allocate memory for host to write data (or malloc for non-TinyGo) |
plugin_free |
(ptr: u32) |
Free allocated memory (optional, or free for non-TinyGo) |
get_name |
() -> u64 |
Returns packed ptr+len of plugin name |
http_transport_intercept |
(ctx_ptr, ctx_len, req_ptr, req_len: u32) -> u64 |
HTTP transport intercept |
pre_hook |
(ctx_ptr, ctx_len, req_ptr, req_len: u32) -> u64 |
Pre-request hook |
post_hook |
(ctx_ptr, ctx_len, resp_ptr, resp_len, err_ptr, err_len: u32) -> u64 |
Post-response hook |
cleanup |
() -> i32 |
Cleanup resources (0 = success) |
init |
(config_ptr, config_len: u32) -> i32 |
Initialize with config (optional) |
Return Value Format
Functions returning data use a packed u64 format:
- Upper 32 bits: pointer to data in WASM memory
- Lower 32 bits: length of data
Data Exchange
All complex data is exchanged as JSON:
HTTPTransportIntercept Input:
ctx:{"request_id": "..."}(context info)req: HTTP request JSON
{
"method": "POST",
"path": "/v1/chat/completions",
"headers": {"Content-Type": "application/json"},
"query": {},
"body": "base64-encoded-body"
}
HTTPTransportIntercept Output:
{
"response": null,
"error": ""
}
To short-circuit, return a response:
{
"response": {
"status_code": 401,
"headers": {"Content-Type": "application/json"},
"body": "base64-encoded-body"
},
"error": ""
}
PreLLMHook Input:
ctx:{"request_id": "..."}(context info)req: Bifrost request JSON
PreLLMHook Output:
{
"request": { ... },
"short_circuit": null,
"error": ""
}
PostLLMHook Input:
ctx: Context JSONresp: Bifrost response JSONerr: Bifrost error JSON (or null)
PostLLMHook Output:
{
"response": { ... },
"bifrost_error": null,
"error": ""
}
Usage with Bifrost
Configure the plugin in your Bifrost config:
{
"plugins": [
{
"path": "/path/to/hello-world.wasm",
"name": "hello-world-wasm",
"enabled": true
}
]
}
Or load from URL:
{
"plugins": [
{
"path": "https://example.com/plugins/hello-world.wasm",
"name": "hello-world-wasm",
"enabled": true
}
]
}
Limitations
WASM plugins have some limitations compared to native .so plugins:
-
Performance: JSON serialization/deserialization adds overhead compared to native plugins.
-
Memory: WASM modules have a linear memory model with limited addressing.
-
TinyGo Constraints: Some Go standard library features are not available in TinyGo.
Benefits
- Cross-platform: Single
.wasmbinary runs on any OS/architecture - Security: WASM provides sandboxed execution
- No CGO: Pure Go compilation, no C dependencies needed on the host
- Portability: Easy to distribute and deploy
- Full feature parity: HTTP transport intercept, PreLLMHook, and PostLLMHook all supported