Repository navigation
feat(playground): show baml.io output in the run view - #4273
Conversation
`baml.io.print` / `println` / `eprint` / `eprintln` returned Unsupported
in both the playground/LSP host and the wasm/webview host. Because those
builtins cannot throw a user value, the error surfaced as a panic, so any
BAML program containing a print worked under `baml run` and died in the
playground. Programs worked around it by shelling out to `/dev/tty`,
which escapes BAML entirely and lands in whatever terminal launched the
server.
Route them to the run instead. Writes become `PayloadKind::Output` events
that stream over the existing run-patch channel and render inline in the
run strip, next to INPUT and RESULT. Process stdout is never touched: under
`baml lsp` it carries the JSON-RPC frames, and under `baml playground` the
user is watching a browser tab.
Rendered through xterm.js rather than an escape-to-HTML converter, because
programs emit real terminal control: cursor movement, `\r` progress bars and
screen clears need a screen buffer to interpret. Chunks are passed through
verbatim and in order, so an escape sequence split across two `print` calls
still resolves. Retained output is capped per run so a runaway print loop
cannot grow the run store without bound.
Also stop the env dialog from blocking runs on optional keys. Env resolution
now prompts only for keys the project declares via `env.FOO`; a key reached
through `baml.env.get("...")` resolves to unset immediately and silently,
since `EnvVarRequest` is what badges a key required and opens the dialog.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01VWbXTcLbNVtm3Xmd5aWajK
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThe PR limits playground environment prompts to declared keys and adds streamed ChangesEnvironment resolution
Streaming BAML IO output
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant BamlIo
participant InMemoryRunStore
participant PlaygroundClient
participant RunOutputTerminal
BamlIo->>InMemoryRunStore: Write stdout/stderr output payload
InMemoryRunStore-->>PlaygroundClient: Broadcast RunPatch
PlaygroundClient->>RunOutputTerminal: Project ordered output chunks
RunOutputTerminal-->>RunOutputTerminal: Render and fit terminal output
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
⏭️ Performance benchmarks were skippedPerf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to To run them on this PR, do any of the following, then push a commit (or re-run CI):
|
Binary size checks passed✅ 7 passed
Generated by |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
typescript2/pkg-playground/src/ExecutionPanel.tsx (1)
3597-3612: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winTest runs don't get the same Output section.
The test-run branch (around Lines 3087-3143) renders input prompts, error, and result but no
RunOutputTerminal, so🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@typescript2/pkg-playground/src/ExecutionPanel.tsx` around lines 3597 - 3612, Add the same output section used by the regular run branch to the test-run rendering path around the test-run result UI, using the test run’s outputChunks and id with RunOutputTerminal. Render it when outputChunks is non-empty while preserving the existing test input, error, and result sections.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@baml_language/crates/baml_lsp_server/src/playground_server.rs`:
- Around line 1612-1614: Update the successful playground_update_source_file
path to refresh WsState.env_state by calling set_declared_keys with the current
state.bex.all_env_var_names(). Keep the existing initialization update
unchanged, ensuring removed environment keys stop prompting and newly referenced
keys are recognized without reconnecting.
In `@typescript2/pkg-playground/src/RunOutputTerminal.tsx`:
- Around line 91-96: Update the cleanup function that disposes the terminal to
reset writtenRef.current to its initial cursor value and clear the pending write
buffer, ensuring a newly mounted terminal replays all chunks from the beginning.
- Around line 134-141: Update the output-writing flow around Term.write so
buffer.active.length is measured only after the write completion callback runs,
then defer the measurement by a requestAnimationFrame UI tick before updating
rows and calling fit(). Preserve the existing MIN_ROWS/MAX_ROWS clamping and
no-op state update behavior.
---
Nitpick comments:
In `@typescript2/pkg-playground/src/ExecutionPanel.tsx`:
- Around line 3597-3612: Add the same output section used by the regular run
branch to the test-run rendering path around the test-run result UI, using the
test run’s outputChunks and id with RunOutputTerminal. Render it when
outputChunks is non-empty while preserving the existing test input, error, and
result sections.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 09b9b146-182f-4f63-8acb-67cdb431a848
⛔ Files ignored due to path filters (1)
typescript2/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (12)
baml_language/crates/baml_lsp_server/src/playground_env.rsbaml_language/crates/baml_lsp_server/src/playground_io.rsbaml_language/crates/baml_lsp_server/src/playground_server.rsbaml_language/crates/bex_events/src/run.rsbaml_language/crates/bex_events/src/run_wire.rsbaml_language/crates/bridge_wasm/src/wasm_io.rstypescript2/pkg-playground/package.jsontypescript2/pkg-playground/src/ExecutionPanel.tsxtypescript2/pkg-playground/src/RunOutputTerminal.tsxtypescript2/pkg-playground/src/run-store-projections.test.tstypescript2/pkg-playground/src/run-store-projections.tstypescript2/pkg-playground/src/worker-protocol.ts
- Reset the write cursor when the terminal is disposed. Refs outlive the emulator, so a remount (StrictMode's dev double-invoke, or any parent remount) rebuilt an empty screen while the chunk loop still started at N, losing every chunk written before the remount. - Measure the buffer in `write`'s completion callback. `write` is async, so reading `buffer.active.length` synchronously sized the box against the previous frame and lagged one update behind. - Render Output for test runs too. The payloads were already projected onto the display run; only the function-run branch rendered them. - Refresh declared env keys when playground source is updated, not just on WebSocket connect. An edit that adds or removes an `env.FOO` reference otherwise left a removed key prompting and a new one silently unset until the session reconnected. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01VWbXTcLbNVtm3Xmd5aWajK
The bug, in one line
Any BAML program with a
printin it worked underbaml runand crashed in the playground.baml.io.print/println/eprint/eprintlnreturnedUnsupportedin the playground/LSP host (playground_io.rs) and the wasm/webview host (wasm_io.rs). Those builtins can't throw a user value, so the error came out as a panic and took the run down.Real programs worked around it by escaping BAML entirely:
/dev/ttytargets the host process's controlling terminal, so output landed in whatever terminal launchedbaml playgroundand was invisible to the UI.What it does now
Stream writes become run events and render inline in the run strip, between the request rows and
RESULT:Process stdout is never touched. Under
baml lspit carries the JSON-RPC frames, so one stray byte desyncs the editor's language client. Underbaml playgroundthe user is looking at a browser tab, not a terminal. Both hosts route to the run instead.Why a terminal emulator and not escape-to-HTML
BAML programs emit real terminal control, not just colors. Cursor movement,
\rprogress bars, and\x1b[2Jscreen clears need a screen buffer to interpret.anser/ansi-to-react(already used in app-website) only translate SGR color codes, so those sequences would render as garbage.xterm.js is lazily imported, so it stays out of the bundle until a run actually prints (
dist/assets/xterm-*.jsis its own chunk).The corollary is that the projection must stay dumb. Chunks are passed through verbatim and in order, never split into lines or regrouped per stream, because a single escape sequence can straddle two
printcalls:xterm.write()buffers partial sequences across calls, so this resolves correctly. There's a test for exactly this case.Data flow
flowchart LR A["baml.io.println"] --> B["PlaygroundIo::write_output<br/>(or WasmIo)"] B --> C["run_store.ingest_output<br/>PayloadKind::Output"] C --> D["broadcast_run_patch"] D --> E["runToOutputChunks"] E --> F["RunOutputTerminal<br/>xterm.js"] B -.->|never| G["process stdout<br/>= JSON-RPC under baml lsp"]Env vars: stop blocking runs on optional keys
Second fix, same area. The env dialog popped on every run and blocked until answered, including for keys with a
??fallback that the program didn't need.Resolution is now: user overrides → process env → prompt only if the project declares the key → unset.
"Declared" means it came from
all_env_var_names(), i.e.env.FOOin a client. A key reached throughbaml.env.get("...")resolves to unset immediately and silently. The silence is load-bearing:EnvVarRequestis what makes the UI badge a key as required and open the dialog, so there's no notify-only channel. Sending one "just so you know" reopens the modal every run.ANTHROPIC_API_KEYenv.Xin a clientBAMLCODE_MEMORY_DIRbaml.env.get("...")NO_COLORbaml.env.get("...")Declared keys are replaced rather than accumulated on each project compile, so a key you stop referencing stops blocking.
Bounded by construction
record.run.payloadsis unbounded per run, so a tight print loop could grow the run store without limit. Output is capped at 1 MiB per run with exactly one truncation notice, then further writes are dropped. Tested.A write that can't be attributed to a live run is dropped rather than failed: panicking a program over an unroutable debug print costs more than the lost line.
Test plan
cargo test -p bex_events— 154 passcargo test -p baml_lsp_server— 68 passpnpm --filter @b/pkg-playground test— 128 passcargo clippy -p bex_events -p baml_lsp_server --all-targets— cleancargo clippy -p bridge_wasm --target wasm32-unknown-unknown— cleantsc --noEmit— cleanbamlcode), output renders with ANSI intactNew tests:
print_writes_are_recorded_verbatim_on_the_runprint_from_an_unattached_call_is_dropped_not_failedingest_output_caps_a_runaway_print_loopingest_output_without_an_attached_host_call_records_nothingundeclared_key_resolves_to_unset_without_waiting_or_promptingReady, notAsync; emits noEnvVarRequestdeclared_keys_are_replaced_not_accumulatedkeeps baml.io output chunks verbatim, in order(TS)Not in this PR
bex_events/src/history); output needs a new one.baml.sys.shellinheriting the host's stdin. With nostdinoption,run_processleaves the child's stdin inherited (io_impls.rs:1128). Underbaml lspthat's the JSON-RPC stream, so a shell command that reads stdin steals protocol bytes. Same shape as the stdout problem, opposite direction. Worth a follow-up.Math.max(1.5, ...)inrun-store-projections.tsfloors bar width at 1.5% of the whole run, so in a 100s trace everything under ~1.5s renders identically. The floor belongs in CSS as a pixel minimum, not in data space. Separate change.Callers must update
Programs using the
/dev/ttyworkaround should switch tobaml.io.print(note:print, notprintln, if the strings already carry their own\n). The one thing that doesn't port cleanly is EOF:shellsignalled it via exit code, andbaml.io.inputhas no EOF channel.🤖 Generated with Claude Code
https://claude.ai/code/session_01VWbXTcLbNVtm3Xmd5aWajK
Summary by CodeRabbit
New Features
baml.ioprint,println,eprint, andeprintln, rendered in a streaming terminal view.Bug Fixes