Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion .github/workflows/js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,48 @@ jobs:
doppler run --only-secrets OPENAI_API_KEY -- ${{ matrix.run }} vercel_ai_tool.mjs
doppler run --only-secrets OPENAI_API_KEY -- ${{ matrix.run }} langchain_agent.mjs

# Keeps the wasm32-wasip1-threads target from silently rotting between
# releases: publish-js.yml builds it with --release, so a PR that breaks
# the wasm feature gating would otherwise only surface at publish time.
# Debug profile here — same compile errors, much faster.
wasm-build:
name: WASM build (wasm32-wasip1-threads)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
with:
targets: wasm32-wasip1-threads

- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2

- name: Setup pnpm
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6
with:
version: 10.33.0

- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: "22"
cache: pnpm
cache-dependency-path: crates/bashkit-js/pnpm-lock.yaml

- name: Install dependencies
run: pnpm install --frozen-lockfile
working-directory: crates/bashkit-js

- name: Build wasm binding
run: pnpm exec napi build --platform --target wasm32-wasip1-threads
working-directory: crates/bashkit-js

# Gate job for branch protection
js-check:
name: JS Check
if: always()
needs: [typecheck, build-and-test]
needs: [typecheck, build-and-test, wasm-build]
runs-on: ubuntu-latest
steps:
- name: Verify all jobs passed
Expand All @@ -246,3 +283,7 @@ jobs:
echo "JS build/test failed"
exit 1
fi
if [[ "${{ needs.wasm-build.result }}" != "success" ]]; then
echo "WASM build failed"
exit 1
fi
18 changes: 11 additions & 7 deletions .github/workflows/publish-js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,13 @@ jobs:
- host: windows-latest
target: x86_64-pc-windows-msvc
build: pnpm exec napi build --platform --release --target x86_64-pc-windows-msvc && pnpm run build:cjs && pnpm run build:ts
# TODO: WASM disabled — tokio "full" features (from bashkit core) are
# unsupported on wasm32. Needs architectural fix to gate tokio features.
# - host: ubuntu-latest
# target: wasm32-wasip1-threads
# build: pnpm exec napi build --platform --release --target wasm32-wasip1-threads && pnpm run build:cjs && pnpm run build:ts
# wasm32 builds bashkit with a reduced feature set (no interop/
# sqlite/http_client/realfs — see crates/bashkit-js/Cargo.toml for
# the per-feature reasons) so tokio stays on the wasm-compatible
# current-thread runtime.
- host: ubuntu-latest
target: wasm32-wasip1-threads
build: pnpm exec napi build --platform --release --target wasm32-wasip1-threads && pnpm run build:cjs && pnpm run build:ts

steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
Expand Down Expand Up @@ -375,8 +377,10 @@ jobs:
sh -c "node examples/openai_tool.mjs && node examples/vercel_ai_tool.mjs && node examples/langchain_agent.mjs"

# ============================================================================
# Test WASI target — disabled until tokio feature gating is resolved
# TODO: Re-enable when bashkit core gates tokio "full" behind a feature flag
# Test WASI target — the tokio feature gating that blocked this is resolved
# (the wasm32-wasip1-threads matrix entry above builds), but running the
# test suite under the WASI runtime is unverified. TODO: re-enable after
# confirming the ava suite passes with NAPI_RS_FORCE_WASI=1.
# ============================================================================
# test-js-wasi:
# name: Test WASI target
Expand Down
18 changes: 17 additions & 1 deletion crates/bashkit-js/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,27 @@ repository.workspace = true
crate-type = ["cdylib"]

[dependencies]
bashkit = { path = "../bashkit", features = ["scripted_tool", "python", "realfs", "jq", "interop", "http_client", "sqlite"] }
napi = { workspace = true }
napi-derive = { workspace = true }
serde_json = { workspace = true }
tokio = { version = "1", features = ["sync", "macros", "io-util", "rt", "time"] }

# wasm drops four features present on every other target (see the matching
# `#[cfg(not(target_arch = "wasm32"))]` gates in src/lib.rs):
# - `interop`/`sqlite`: both force `tokio/rt-multi-thread` on, which pulls in
# mio -> socket2, and socket2 hard-fails to compile on any wasm target.
# - `http_client`: reqwest's TCP client needs `tokio::net` (real OS sockets),
# which WASI preview1 doesn't expose. reqwest's fetch-based wasm backend
# only targets wasm32-unknown-unknown (browser via web-sys), not
# wasm32-wasip1-threads, so there's no working backend to fall back to here.
# - `realfs`: mounts the *host* filesystem into the sandbox. Meaningless in a
# browser (no host filesystem to mount) and needs tokio/fs, which is only
# pulled in on non-wasm (see crates/bashkit/Cargo.toml).
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
bashkit = { path = "../bashkit", features = ["scripted_tool", "python", "realfs", "jq", "interop", "http_client", "sqlite"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
bashkit = { path = "../bashkit", features = ["scripted_tool", "python", "jq"] }

[build-dependencies]
napi-build = { workspace = true }
12 changes: 10 additions & 2 deletions crates/bashkit-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,14 @@
"aarch64-apple-darwin",
"x86_64-unknown-linux-gnu",
"aarch64-unknown-linux-gnu",
"x86_64-pc-windows-msvc"
]
"x86_64-pc-windows-msvc",
"wasm32-wasip1-threads"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the wasm target makes the package's existing browser export (./bashkit.wasi-browser.js) active for browser consumers, but the generated file exports the raw napi bindings only. It does not expose the wrapper API advertised by wrapper.d.ts/README, such as FileSystem, Bash.create, or the wrapper custom-builtin adaptation. Please either route the browser condition through a wrapper that re-exports the public API, or expose this as a separate raw wasm entrypoint with matching types/docs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rechecked dd90fc8a merged onto current origin/main (df4acc21). This still reproduces: bashkit.wasi-browser.js exports Bash, BashTool, ScriptedTool, getVersion, and raw __fileSystem* helpers only; it still does not export wrapper API like FileSystem or Bash.create. Since package.json routes the public browser condition to that generated file while types/README describe the wrapper API, this still looks like a browser-consumer API mismatch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! Let me think this through a bit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prototyped both directions you suggested, verified each end-to-end in headless Chromium against a Vite terminal example (COOP/COEP, wasip1-threads build), and pushed them as single-commit branches on top of this PR so you can compare:

Option A — raw wasm as an opt-in subpath (compare, +58/−2): removes the root browser condition entirely and exposes the raw napi surface as @everruns/bashkit/wasm (browser → wasi-browser loader, default → wasi cjs loader), typed by a new wasm.d.ts that documents exactly what the raw surface does not include. Browser consumers opt in knowingly; the root import never lies. Cost: two documented API surfaces, and no FileSystem/Bash.create/custom builtins in browsers.

Option B — wrapper API everywhere (compare, +267/−49): makes wrapper.ts platform-neutral so browsers get the same root entrypoint and the same documented API. The platform split lives behind a self-referencing ./binding subpath export (binding-node.ts: createRequire + AsyncResource; binding-browser.ts: wasm loader + identity binding + TextEncoder for the Buffer uses). Natives compiled out of wasm (__realFileSystem, __importFileSystem, __fileSystemToExternal) throw a descriptive "not available in the wasm build" error. Verified in-browser: FileSystem ops, Bash.create with customBuiltins (JS callbacks work through emnapi), and onOutput — with one caveat below. Full ava suite still green on the native path.

Caveat that affects both options: executeWithOutput deadlocks on the wasm build — zero chunks delivered, hangs forever, reproduced in both browsers and Node WASI (NAPI_RS_FORCE_WASI=1). The per-chunk blocking ThreadsafeFunction round-trip (waiting on the JS callback's return value) doesn't survive the wasm threading model. Option B works around it (supportsStreamingOutput binding flag; wrapper falls back to plain execute + one final chunk, documented in README); option A would expose it raw. Either way the native fix in src/lib.rs looks like a separate follow-up.

My lean is Option B — it resolves the mismatch you flagged rather than fencing it off, and the browser demo shows the wrapper API genuinely works on wasm. But happy to go either way; whichever you prefer I'll fold into this PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, I am ok closing this PR as well. I was doing it mostly as a nice to do. I am unblocked with #2140 . I understand this adds another layer that needs to be maintained. So it will be your call :) Thanks for the quick reviews

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's postpone then and close for now. I would be ready to return back to it when it would became blocker.

],
"wasm": {
"browser": {
"asyncInit": true
}
}
},
"files": [
"wrapper.js",
Expand Down Expand Up @@ -102,6 +108,8 @@
}
},
"devDependencies": {
"@emnapi/core": "1.10.0",
Comment thread
chaliy marked this conversation as resolved.
"@emnapi/runtime": "1.10.0",
"@langchain/core": "^1.1.39",
"@napi-rs/cli": "^3.0.0",
"@types/node": "^25.5.0",
Expand Down
12 changes: 7 additions & 5 deletions crates/bashkit-js/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading