first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:52:23 +03:00
commit 880f412e2c
2662 changed files with 866266 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
.PHONY: all build clean help check-tinygo
# Colors
COLOR_RESET = \033[0m
COLOR_INFO = \033[36m
COLOR_SUCCESS = \033[32m
COLOR_WARNING = \033[33m
COLOR_ERROR = \033[31m
COLOR_BOLD = \033[1m
# Plugin configuration
PLUGIN_NAME = hello-world
OUTPUT_DIR = build
OUTPUT = $(OUTPUT_DIR)/$(PLUGIN_NAME).wasm
# TinyGo build flags
TINYGO_TARGET = wasi
TINYGO_SCHEDULER = none
help: ## Show this help message
@echo '$(COLOR_BOLD)Hello World WASM Plugin$(COLOR_RESET)'
@echo ''
@echo '$(COLOR_BOLD)Usage:$(COLOR_RESET) make [target]'
@echo ''
@echo '$(COLOR_BOLD)Prerequisites:$(COLOR_RESET)'
@echo ' - TinyGo (https://tinygo.org/getting-started/install/)'
@echo ' macOS: brew install tinygo'
@echo ' Linux: See TinyGo installation docs'
@echo ''
@echo '$(COLOR_BOLD)Available targets:$(COLOR_RESET)'
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " $(COLOR_INFO)%-15s$(COLOR_RESET) %s\n", $$1, $$2}' $(MAKEFILE_LIST)
check-tinygo: ## Check if TinyGo is installed
@which tinygo > /dev/null 2>&1 || (echo "$(COLOR_ERROR)Error: TinyGo is not installed$(COLOR_RESET)"; \
echo "$(COLOR_INFO)Install TinyGo:$(COLOR_RESET)"; \
echo " macOS: brew install tinygo"; \
echo " Linux: See https://tinygo.org/getting-started/install/"; \
exit 1)
@echo "$(COLOR_SUCCESS)✓ TinyGo found: $$(tinygo version)$(COLOR_RESET)"
build: check-tinygo ## Build the WASM plugin
@mkdir -p $(OUTPUT_DIR)
@echo "$(COLOR_INFO)Building WASM plugin...$(COLOR_RESET)"
GOWORK=off tinygo build -o $(OUTPUT) -target=$(TINYGO_TARGET) -scheduler=$(TINYGO_SCHEDULER) .
@echo "$(COLOR_SUCCESS)✓ Plugin built successfully: $(OUTPUT)$(COLOR_RESET)"
@ls -lh $(OUTPUT) | awk '{print " Size: " $$5}'
build-optimized: check-tinygo ## Build the WASM plugin with size optimizations
@mkdir -p $(OUTPUT_DIR)
@echo "$(COLOR_INFO)Building optimized WASM plugin...$(COLOR_RESET)"
GOWORK=off tinygo build -o $(OUTPUT) -target=$(TINYGO_TARGET) -scheduler=$(TINYGO_SCHEDULER) -no-debug -gc=leaking .
@echo "$(COLOR_SUCCESS)✓ Optimized plugin built: $(OUTPUT)$(COLOR_RESET)"
@ls -lh $(OUTPUT) | awk '{print " Size: " $$5}'
clean: ## Remove build artifacts
@echo "$(COLOR_INFO)Cleaning build artifacts...$(COLOR_RESET)"
@rm -rf $(OUTPUT_DIR)
@echo "$(COLOR_SUCCESS)✓ Clean complete$(COLOR_RESET)"
info: ## Show build information
@echo "$(COLOR_BOLD)Build Configuration$(COLOR_RESET)"
@echo " Plugin Name: $(PLUGIN_NAME)"
@echo " Output: $(OUTPUT)"
@echo " Target: $(TINYGO_TARGET)"
@echo " Scheduler: $(TINYGO_SCHEDULER)"
@echo ""
@if [ -f "$(OUTPUT)" ]; then \
echo "$(COLOR_SUCCESS)Plugin exists:$(COLOR_RESET)"; \
ls -lh $(OUTPUT) | awk '{print " " $$9 " (" $$5 ")"}'; \
else \
echo "$(COLOR_WARNING)Plugin not built yet$(COLOR_RESET)"; \
fi
.DEFAULT_GOAL := help

View File

@@ -0,0 +1,170 @@
# 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:**
```bash
brew install tinygo
```
**Linux (Ubuntu/Debian):**
```bash
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](https://tinygo.org/getting-started/install/)
## Building
```bash
# 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
```json
{
"method": "POST",
"path": "/v1/chat/completions",
"headers": {"Content-Type": "application/json"},
"query": {},
"body": "base64-encoded-body"
}
```
**HTTPTransportIntercept Output:**
```json
{
"response": null,
"error": ""
}
```
To short-circuit, return a response:
```json
{
"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:**
```json
{
"request": { ... },
"short_circuit": null,
"error": ""
}
```
**PostLLMHook Input:**
- `ctx`: Context JSON
- `resp`: Bifrost response JSON
- `err`: Bifrost error JSON (or null)
**PostLLMHook Output:**
```json
{
"response": { ... },
"bifrost_error": null,
"error": ""
}
```
## Usage with Bifrost
Configure the plugin in your Bifrost config:
```json
{
"plugins": [
{
"path": "/path/to/hello-world.wasm",
"name": "hello-world-wasm",
"enabled": true
}
]
}
```
Or load from URL:
```json
{
"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:
1. **Performance**: JSON serialization/deserialization adds overhead compared to native plugins.
2. **Memory**: WASM modules have a linear memory model with limited addressing.
3. **TinyGo Constraints**: Some Go standard library features are not available in TinyGo.
## Benefits
1. **Cross-platform**: Single `.wasm` binary runs on any OS/architecture
2. **Security**: WASM provides sandboxed execution
3. **No CGO**: Pure Go compilation, no C dependencies needed on the host
4. **Portability**: Easy to distribute and deploy
5. **Full feature parity**: HTTP transport intercept, PreLLMHook, and PostLLMHook all supported

View File

@@ -0,0 +1,30 @@
module github.com/maximhq/bifrost/examples/plugins/hello-world-wasm
go 1.26.2
require github.com/maximhq/bifrost/core v1.4.17
require (
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.2 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/mailru/easyjson v0.9.1 // indirect
github.com/mark3labs/mcp-go v0.43.2 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.68.0 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
golang.org/x/arch v0.23.0 // indirect
golang.org/x/sys v0.42.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -0,0 +1,83 @@
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mark3labs/mcp-go v0.43.2 h1:21PUSlWWiSbUPQwXIJ5WKlETixpFpq+WBpbMGDSVy/I=
github.com/mark3labs/mcp-go v0.43.2/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
github.com/maximhq/bifrost/core v1.3.3 h1:r2llMAfzIHeSxwY2L55UaSOsY17JSg5zYcqF2JtaRVY=
github.com/maximhq/bifrost/core v1.3.3/go.mod h1:abKQRnJQPZz8/UMxCcbuNHEyq19Db+IX4KlGJdlLY8E=
github.com/maximhq/bifrost/core v1.4.17/go.mod h1:O6VEP2MHkQgo1iLYoxGQ7a+3VBBlHoETCH+pOR6Q5X8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.68.0 h1:v12Nx16iepr8r9ySOwqI+5RBJ/DqTxhOy1HrHoDFnok=
github.com/valyala/fasthttp v1.68.0/go.mod h1:5EXiRfYQAoiO/khu4oU9VISC/eVY6JqmSpPJoHCKsz4=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,226 @@
// Package main provides a hello-world WASM plugin example for Bifrost.
// This plugin demonstrates the basic structure and exports required for WASM plugins.
//
// Build with TinyGo:
//
// tinygo build -o build/hello-world.wasm -target=wasi -scheduler=none main.go
package main
import (
"encoding/json"
)
// ============================================================================
// Plugin Exports
// ============================================================================
//export get_name
func get_name() uint64 {
return writeBytes([]byte("Hello World WASM Plugin"))
}
//export init
func init_plugin(configPtr, configLen uint32) int32 {
println("WASM Plugin: Init called")
if configLen > 0 {
configData := readInput(configPtr, configLen)
println("WASM Plugin: Config received:", string(configData))
}
return 0
}
//export http_intercept
func http_intercept(inputPtr, inputLen uint32) uint64 {
println("WASM Plugin: http_intercept called")
inputData := readInput(inputPtr, inputLen)
if inputData == nil {
return writeError("no input data")
}
// Parse input
var input HTTPInterceptInput
if err := json.Unmarshal(inputData, &input); err != nil {
println("WASM Plugin: parse error:", err.Error())
return writeError("parse error: " + err.Error())
}
// Log parsed data
println("WASM Plugin: HTTP", input.Request.Method, input.Request.Path)
if ct, ok := input.Request.Headers["content-type"]; ok {
println("WASM Plugin: Content-Type:", ct)
}
input.Context["from-http"] = "123"
// Return pass-through
output := HTTPInterceptOutput{
Context: input.Context,
Request: input.Request,
HasResponse: false,
Error: "",
}
data, _ := json.Marshal(output)
return writeBytes(data)
}
//export pre_hook
func pre_hook(inputPtr, inputLen uint32) uint64 {
println("WASM Plugin: pre_hook called")
inputData := readInput(inputPtr, inputLen)
if inputData == nil {
return writePreHookError("no input data")
}
println("WASM Plugin: Pre-hook input:", string(inputData))
// Parse input
var input PreHookInput
if err := json.Unmarshal(inputData, &input); err != nil {
println("WASM Plugin: parse error:", err.Error())
return writePreHookError("parse error: " + err.Error())
}
// Print existing context
for k, v := range input.Context {
println("WASM Plugin: Context", k, "=", v)
}
input.Context["from-pre-hook"] = "789"
// Return with custom context value
output := PreHookOutput{
Context: input.Context,
Request: input.Request,
HasShortCircuit: false,
Error: "",
}
data, _ := json.Marshal(output)
return writeBytes(data)
}
//export post_hook
func post_hook(inputPtr, inputLen uint32) uint64 {
println("WASM Plugin: post_hook called")
inputData := readInput(inputPtr, inputLen)
if inputData == nil {
return writePostHookError("no input data")
}
// Parse input
var input PostHookInput
if err := json.Unmarshal(inputData, &input); err != nil {
println("WASM Plugin: parse error:", err.Error())
return writePostHookError("parse error: " + err.Error())
}
println("WASM Plugin: Post-hook input:", string(inputData))
// Print existing context
for k, v := range input.Context {
println("WASM Plugin: Context", k, "=", v)
}
// Parse response for logging
if processed, ok := input.Context["wasm_plugin_processed"].(bool); ok && processed {
println("WASM Plugin: Pre-hook context value present")
}
input.Context["from-post-hook"] = "456"
// Return pass-through
output := PostHookOutput{
Context: input.Context,
Response: input.Response,
Error: input.Error,
HasError: false,
HookError: "",
}
data, _ := json.Marshal(output)
return writeBytes(data)
}
//export http_stream_chunk_hook
func http_stream_chunk_hook(inputPtr, inputLen uint32) uint64 {
println("WASM Plugin: http_stream_chunk_hook called")
inputData := readInput(inputPtr, inputLen)
if inputData == nil {
return writeStreamChunkError("no input data")
}
// Parse input
var input HTTPStreamChunkHookInput
if err := json.Unmarshal(inputData, &input); err != nil {
println("WASM Plugin: parse error:", err.Error())
return writeStreamChunkError("parse error: " + err.Error())
}
println("WASM Plugin: Stream chunk received")
// Add context value
input.Context["from-stream-chunk"] = "wasm-plugin"
// Pass through chunk unchanged
output := HTTPStreamChunkHookOutput{
Context: input.Context,
Chunk: input.Chunk,
HasChunk: true,
Skip: false,
Error: "",
}
data, _ := json.Marshal(output)
return writeBytes(data)
}
//export cleanup
func cleanup() int32 {
println("WASM Plugin: Cleanup called")
return 0
}
// Helper functions for error responses
func writeError(msg string) uint64 {
output := HTTPInterceptOutput{HasResponse: false, Error: msg}
data, _ := json.Marshal(output)
return writeBytes(data)
}
func writePreHookError(msg string) uint64 {
output := PreHookOutput{
Context: map[string]interface{}{},
Request: nil,
HasShortCircuit: false,
Error: msg,
}
data, _ := json.Marshal(output)
return writeBytes(data)
}
func writePostHookError(msg string) uint64 {
output := PostHookOutput{
Context: map[string]interface{}{},
Response: nil,
HasError: false,
HookError: msg,
}
data, _ := json.Marshal(output)
return writeBytes(data)
}
func writeStreamChunkError(msg string) uint64 {
output := HTTPStreamChunkHookOutput{
Context: map[string]interface{}{},
Chunk: nil,
HasChunk: false,
Skip: false,
Error: msg,
}
data, _ := json.Marshal(output)
return writeBytes(data)
}
func main() {}

View File

@@ -0,0 +1,89 @@
package main
import "unsafe"
// ============================================================================
// Memory Management
// ============================================================================
// heapSize is the fixed size of the pre-allocated heap.
// This must be large enough to handle all allocations during the plugin lifetime.
// The heap is never reallocated to ensure all pointers remain valid.
const heapSize = 4 * 1024 * 1024 // 4MB fixed heap
// heapBase is a fixed-size buffer that is never reallocated.
// All allocations come from this buffer to ensure pointer stability.
var heapBase []byte
// heapOffset tracks the next available position in heapBase.
var heapOffset uint32 = 0
// heapBasePtr caches the base pointer of heapBase for efficient offset-to-pointer conversion.
var heapBasePtr uintptr
func init() {
// Pre-allocate the fixed heap once at startup.
// This ensures heapBase is never reallocated after pointers are handed out.
heapBase = make([]byte, heapSize)
heapBasePtr = uintptr(unsafe.Pointer(&heapBase[0]))
}
//export plugin_malloc
func plugin_malloc(size uint32) uint32 {
if size == 0 {
return 0
}
// Align to 8-byte boundary
alignedSize := (size + 7) &^ 7
// Check if we have enough space (no reallocation allowed)
if heapOffset+alignedSize > uint32(len(heapBase)) {
// Allocation failure - heap exhausted
// Return 0 to indicate failure rather than reallocating
return 0
}
// Return pointer to the allocated region
ptr := uint32(heapBasePtr + uintptr(heapOffset))
heapOffset += alignedSize
return ptr
}
//export plugin_free
func plugin_free(ptr uint32) {
// No-op: we use a simple bump allocator without individual frees.
// Memory is reclaimed when the plugin is unloaded.
}
// plugin_reset resets the heap allocator, allowing memory to be reused.
// This should only be called when no allocated memory is in use.
//
//export plugin_reset
func plugin_reset() {
heapOffset = 0
}
func packResult(ptr uint32, length uint32) uint64 {
return (uint64(ptr) << 32) | uint64(length)
}
func writeBytes(data []byte) uint64 {
if len(data) == 0 {
return 0
}
// Allocate from the stable heap
ptr := plugin_malloc(uint32(len(data)))
if ptr == 0 {
// Allocation failed
return 0
}
// Copy data into the allocated region
offset := ptr - uint32(heapBasePtr)
copy(heapBase[offset:offset+uint32(len(data))], data)
return packResult(ptr, uint32(len(data)))
}
func readInput(ptr, length uint32) []byte {
if length == 0 {
return nil
}
return unsafe.Slice((*byte)(unsafe.Pointer(uintptr(ptr))), length)
}

View File

@@ -0,0 +1,70 @@
package main
import "github.com/maximhq/bifrost/core/schemas"
// ============================================================================
// Input/Output Structs
// ============================================================================
// HTTPInterceptInput is the input for http_intercept
type HTTPInterceptInput struct {
Context map[string]interface{} `json:"context"`
Request *schemas.HTTPRequest `json:"request,omitempty"`
}
// HTTPInterceptOutput is the output for http_intercept
type HTTPInterceptOutput struct {
Context map[string]interface{} `json:"context"`
Request *schemas.HTTPRequest `json:"request,omitempty"`
Response *schemas.HTTPResponse `json:"response,omitempty"`
HasResponse bool `json:"has_response"`
Error string `json:"error"`
}
// PreHookInput is the input for pre_hook
type PreHookInput struct {
Context map[string]interface{} `json:"context"`
Request *schemas.BifrostRequest `json:"request,omitempty"` // Keep raw for pass-through
}
// PreHookOutput is the output for pre_hook
type PreHookOutput struct {
Context map[string]interface{} `json:"context"`
Request *schemas.BifrostRequest `json:"request,omitempty"`
ShortCircuit *schemas.LLMPluginShortCircuit `json:"short_circuit,omitempty"`
HasShortCircuit bool `json:"has_short_circuit"`
Error string `json:"error"`
}
// PostHookInput is the input for post_hook
type PostHookInput struct {
Context map[string]interface{} `json:"context"`
Response *schemas.BifrostResponse `json:"response,omitempty"`
Error *schemas.BifrostError `json:"error,omitempty"`
HasError bool `json:"has_error"`
}
// PostHookOutput is the output for post_hook
type PostHookOutput struct {
Context map[string]interface{} `json:"context"`
Response *schemas.BifrostResponse `json:"response,omitempty"`
Error *schemas.BifrostError `json:"error,omitempty"`
HasError bool `json:"has_error"`
HookError string `json:"hook_error"`
}
// HTTPStreamChunkHookInput is the input for http_stream_chunk_hook
type HTTPStreamChunkHookInput struct {
Context map[string]interface{} `json:"context"`
Request *schemas.HTTPRequest `json:"request,omitempty"`
Chunk *schemas.BifrostStreamChunk `json:"chunk,omitempty"`
}
// HTTPStreamChunkHookOutput is the output for http_stream_chunk_hook
type HTTPStreamChunkHookOutput struct {
Context map[string]interface{} `json:"context"`
Chunk *schemas.BifrostStreamChunk `json:"chunk,omitempty"`
HasChunk bool `json:"has_chunk"`
Skip bool `json:"skip"`
Error string `json:"error"`
}