packages/native/bun-runtime/README.md
This package owns the full Bun engine port for iOS. It intentionally lives under
packages/ so the repo does not need a separate top-level native workspace.
The current upstream Bun release does not publish an iOS target for
bun build --compile; the supported standalone executable targets are Linux,
macOS, and Windows. A real phone build therefore needs an embeddable iOS
framework produced from a Bun fork, not a macOS Bun executable copied into an
iOS app bundle.
The app build looks for this framework when ELIZA_IOS_FULL_BUN_ENGINE=1:
packages/native/bun-runtime/artifacts/ElizaBunEngine.xcframework
You can override the path with:
ELIZA_IOS_BUN_ENGINE_XCFRAMEWORK=/absolute/path/ElizaBunEngine.xcframework
The app build validates an override first, then stages it into
packages/native/bun-runtime/artifacts/ before CocoaPods runs. The Podspec only
accepts vendored frameworks inside this package so full-Bun builds do not depend
on an external absolute path at install/sign time.
If full-engine mode is requested and the framework is missing, the iOS build fails before CocoaPods instead of falling back to the JSContext compatibility host.
The harness follows the same practical shape as the public mobile runtime examples:
dannote/pi-ios and dannote/bun prove the iOS Bun path by building
JavaScriptCore in JSCOnly / C_LOOP / no-JIT mode, then linking Bun as app
code that exposes bun_start(...).iOSExpertise/nodejs-mobile uses the same packaging pattern we need here:
build static mobile runtime pieces first, then wrap them in an iOS framework
that Xcode/CocoaPods can sign and embed.The package supplies the Eliza ABI shim in
Sources/ElizaBunEngineShim/. A Bun fork can either export the Eliza ABI
directly or expose the src/ios/bun_ios.h style API used by dannote/bun; the
build script wraps that Bun API into ElizaBunEngine.framework.
The exported Eliza ABI is documented in BRIDGE_CONTRACT.md:
eliza_bun_engine_abi_versioneliza_bun_engine_last_erroreliza_bun_engine_set_host_callbackeliza_bun_engine_starteliza_bun_engine_stopeliza_bun_engine_is_runningeliza_bun_engine_calleliza_bun_engine_freeFull-engine builds link this framework directly through CocoaPods and the Swift
module import, so production/App Store builds do not import dynamic-loader APIs.
Compatibility/debug builds keep an optional loader path so the JSContext bridge
can run without embedding the full engine framework. When the framework exists,
start() defaults to engine: "auto" and will boot the full engine. Passing
engine: "bun" requires the framework and returns an error if it is missing.
Device/App Store builds of ElizaBunEngine.xcframework must be no-JIT and
must not depend on unsigned executable memory, downloaded native code, helper
executables, or arbitrary dynamic library loading. The framework metadata
declares this with:
ElizaBunEngineNoJIT = true
ElizaBunEngineExecutionProfile = ios-app-store-nojit
The build and verifier reject engine binaries that import local code-loading or
JIT-sensitive symbols such as dlopen, dlsym, posix_spawn, fork,
execve, system, pthread_jit_write_protect_np, mach_vm_protect, or
vm_protect. Regular file-backed model/runtime assets remain allowed; they are
data inputs to the signed in-process runtime, not executable payloads.
# Verify the staged xcframework.
bun run --cwd packages/native/bun-runtime verify:app-store
# Verify only the device slice. This must fail until the xcframework contains
# ios-arm64 and the engine imports no dynamic-loader/process/JIT symbols.
bun run --cwd packages/native/bun-runtime verify:app-store -- --target=device
# Verify a signed .app bundle and its embedded ElizaBunEngine framework.
bun run --cwd packages/native/bun-runtime verify:app-store -- --app=/path/to/App.app
# Verify upstream target reality on the current Bun binary.
bun run --cwd packages/native/bun-runtime check
# Build the simulator engine from a fork checkout.
ELIZA_BUN_IOS_SOURCE_DIR=/path/to/elizaos-bun \
bun run --cwd packages/native/bun-runtime build:sim
# Build and require the full engine inside the iOS app.
ELIZA_IOS_FULL_BUN_ENGINE=1 \
bun run --cwd packages/app build:ios:local:sim
By default the build script expects a fork checkout at
packages/native/bun-runtime/vendor/bun or ELIZA_BUN_IOS_SOURCE_DIR. The public
https://github.com/elizaos/bun repository was not available at the time this
package was added, so the scripts do not silently clone or vendor upstream Bun.
The CMake backend is selected automatically when the source checkout has a
CMakeLists.txt. Useful inputs:
# Staged WebKit/JSC output with lib/ and JavaScriptCore/Headers/.
ELIZA_BUN_IOS_WEBKIT_BUILD_DIR=/path/to/WebKitBuild/JSCOnly
# Or a ready include/lib staging directory.
ELIZA_BUN_IOS_WEBKIT_PATH=/path/to/staged-ios-webkit
# Force CMake and pass fork-specific flags.
ELIZA_BUN_IOS_BUILD_BACKEND=cmake
ELIZA_BUN_IOS_CMAKE_ARGS="-DWEBKIT_PATH=/path/to/staged-ios-webkit"
The harness always passes the App Store runtime profile into the Bun fork:
ELIZA_IOS_APP_STORE_LOCAL_EXECUTION=1
ELIZA_IOS_NO_JIT=1
ELIZA_IOS_DISABLE_DYNAMIC_LOADING=1
ELIZA_IOS_DISABLE_PROCESS_SPAWN=1
ELIZA_IOS_DISABLE_BUN_FFI=1
ELIZA_IOS_DISABLE_BUN_SHELL=1
ELIZA_IOS_DISABLE_BUN_SUBPROCESS=1
JSC_useJIT=0
BUN_JSC_useJIT=0
Fork builds should consume those as compile-time guards and compile out
Bun.ffi, native extension loading, Bun.spawn, node:child_process, shell
helpers, package install runners, and executable-memory/JIT permission paths for
the ios-arm64 slice. The verifier groups failures by imported symbol family so
device builds fail on the source feature that remains, not just on a raw nm
line.
When the Bun fork emits libbun-profile.a or CMakeFiles/bun-profile.dir/*.o
plus bun-zig.o, this package links those objects with
Sources/ElizaBunEngineShim/eliza_bun_engine_shim.c, validates the required
symbols, and writes artifacts/ElizaBunEngine.xcframework.
The C shim starts:
public/agent/agent-bundle.js ios-bridge --stdio
packages/agent/src/cli/ios-bridge.ts then boots the real agent runtime and
handles foreground routes over the Capacitor-owned stdio IPC. UI calls flow:
React fetch / Agent.request
-> Capacitor ElizaBunRuntime.call("http_request")
-> ElizaBunEngine C ABI
-> stdio NDJSON
-> agent ios-bridge
-> in-process routes / buffered legacy handlers
Native llama calls use the same channel in the reverse direction. The agent
bundle emits host_call frames, the C shim invokes Swift, and Swift delegates
to LlamaBridgeImpl. No WebView, Bun, or native layer opens a TCP port for the
full-Bun local backend.
Implemented in this repo:
ElizaBunEngine.xcframework.bun_start(...) iOS fork into the Eliza ABI.ios-bridge --stdio command for bridged HTTP requests and
send_message.bun-ios-* compile
target.Still required in the Bun fork:
ElizaBunEngine.xcframework.bun_start(...) compatible with src/ios/bun_ios.h, or export the
Eliza ABI directly.public/agent/agent-bundle.js, then repeat on a
developer-signed sideload/device build.