Conversation
…yground renderers Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughIntroduces a global handle table for FFI boundaries and migrates the protobuf/FFI value protocol from Host/CFFI types to new BAML types (InboundValue/BamlOutboundValue/CallFunctionArgs/BamlHandle). Adds handle lifecycle FFI entry points, updates encoding/decoding across Rust/Python/WASM/TS, removes object-based FFI methods, and adds renderers for media and prompt-AST in the playground. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client/WebSocket
participant Playground as Playground Worker
participant LSP as LSP Server
participant Rust as Rust Runtime
participant HandleTbl as HANDLE_TABLE
Note over Client,Rust: Call flow with handle resolution & encoding
Client->>Playground: callFunction (encoded CallFunctionArgs)
Playground->>LSP: forward call
LSP->>Rust: decode CallFunctionArgs -> kwargs_to_bex_values(..., &HANDLE_TABLE)
Rust->>HandleTbl: resolve referenced handles (if any)
Rust->>Rust: execute function
Rust->>HandleTbl: insert opaque results -> u64 keys
Rust->>Rust: external_to_baml_value(result, HandleTableOptions::for_wire())
Rust-->>LSP: return encoded BamlOutboundValue (base64)
LSP-->>Playground: relay result (base64)
Playground->>Playground: decodeCallResult(base64) -> construct JS BamlHandle wrappers
Playground-->>Client: callFunctionResult (JSON string with handles)
sequenceDiagram
participant JS as JavaScript
participant WASM as WASM Bridge
participant Rust as Rust Runtime
participant HandleTbl as HANDLE_TABLE
JS->>WASM: new BamlHandle(key, type)
JS->>JS: store handle in liveHandles
JS->>WASM: handle.cloneHandle()
WASM->>Rust: HANDLE_TABLE.clone_handle(key)
Rust->>HandleTbl: clone and return new_key
Rust-->>WASM: new_key
WASM-->>JS: BamlHandle(new_key, type)
JS->>Playground: clearHandles(runIds)
Playground->>WASM: release JS-side references
JS->>WASM: GC drop -> WASM calls HANDLE_TABLE.release(key)
HandleTbl-->>Rust: remove entry
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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 |
Binary size checks passed✅ 7 passed
Generated by |
Merging this PR will not alter performance
|
There was a problem hiding this comment.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
typescript2/app-promptfiddle/src/playground/baml-lsp-worker.ts (1)
469-487:⚠️ Potential issue | 🟠 MajorException-path handle leak: partially-created
BamlHandleobjects are never freedIf
decodeCallResultthrows after constructing one or moreBamlHandleinstances (which are pushed intohandles[]), thecatchblock posts the error but never frees those handles. Because they are not stored inliveHandles, theclearHandlespath won't clean them up either, causing WASM memory to leak on every decoding failure.🔒 Proposed fix: free handles in the error path
try { const resultBytes = await runtime.callFunction( msg.id, msg.project, msg.name, msg.argsProto, ); const bytes = new Uint8Array(resultBytes); const handles: BamlHandle[] = []; const decoded = decodeCallResult(bytes, (key, handleType, typeName) => { const h = new BamlHandle(key, handleType); handles.push(h); return h; }); if (handles.length > 0) { liveHandles.set(msg.id, handles); } const result = JSON.stringify(decoded, null, 2); postOut({ type: "callFunctionResult", id: msg.id, result }); } catch (e) { + // Free any handles created before the error to avoid WASM leaks. + for (const h of handles) { + h.free(); + } postOut({ type: "callFunctionError", id: msg.id, error: e instanceof Error ? e.message : String(e), }); }Note:
handlesmust be declared before thetryblock (or thecatchblock must reference it via closure) for this to work:+ const handles: BamlHandle[] = []; try { const resultBytes = await runtime.callFunction(...); const bytes = new Uint8Array(resultBytes); - const handles: BamlHandle[] = []; const decoded = decodeCallResult(bytes, (key, handleType, typeName) => { const h = new BamlHandle(key, handleType); handles.push(h); return h; }); if (handles.length > 0) { liveHandles.set(msg.id, handles); } const result = JSON.stringify(decoded, null, 2); postOut({ type: "callFunctionResult", id: msg.id, result }); } catch (e) { + for (const h of handles) h.free(); postOut({ type: "callFunctionError", id: msg.id, error: e instanceof Error ? e.message : String(e), }); }baml_language/crates/bridge_python/src/runtime.rs (1)
48-48:⚠️ Potential issue | 🟡 MinorStale docstring:
HostFunctionArguments→CallFunctionArgs.Lines 48 and 104 still reference "Protobuf-encoded HostFunctionArguments bytes" but the code now decodes
CallFunctionArgs. Update to match the new proto message name.📝 Proposed fix
- /// * `args_proto` - Protobuf-encoded HostFunctionArguments bytes + /// * `args_proto` - Protobuf-encoded CallFunctionArgs bytesApply to both
call_function(line 48) andcall_function_sync(line 104).Also applies to: 104-108
baml_language/crates/bridge_python/python_src/baml_py/proto.py (1)
84-132: 🧹 Nitpick | 🔵 TrivialNew outbound variants
media_valueandprompt_ast_valuenot handled in decoder.The outbound proto now includes
media_value(tag 17) andprompt_ast_value(tag 18) inBamlOutboundValue. These aren't decoded here — the fallback on line 132 returnsNonesilently.This is likely fine for in-process Python usage (
for_in_process()emits handles, not inline media/prompt_ast). But if this decoder is ever used with wire-mode data, these values would silently becomeNone.Consider adding an explicit branch (even if it just returns a placeholder or raises) for discoverability, or document the assumption.
baml_language/crates/bridge_ctypes/types/baml/cffi/v1/baml_outbound.proto (1)
12-38: 🧹 Nitpick | 🔵 TrivialSkipped field numbers in
BamlOutboundValue(1, 10) — considerreserveddirectives.Fields 1 and 10 are unused in the
oneof. If these were removed from a previous schema version, addingreserved 1, 10;prevents accidental reuse and documents the gap.baml_language/crates/bridge_ctypes/src/value_decode.rs (1)
1-138: 🧹 Nitpick | 🔵 TrivialAdd unit tests for
inbound_to_externalwith handle resolution.The handle table itself is well-tested, but the decoding path — particularly the handle variant (lines 38–43) and recursive handle resolution in nested structures (lines 55, 72, 93, 132) — lacks dedicated unit tests. Rust unit tests are preferred per coding guidelines; consider adding tests for edge cases like invalid handle keys and nested structures with handles.
baml_language/crates/bridge_ctypes/src/value_encode.rs (1)
22-136: 🛠️ Refactor suggestion | 🟠 MajorMissing unit tests for the new encoding logic.
This file contains non-trivial conversion logic — especially the conditional serialization paths for Media/PromptAst (lines 107-117) and the handle-table fallback (lines 119-132). Unit tests would be valuable to verify:
- Media/PromptAst are serialized inline when the corresponding flags are
true.- Media/PromptAst are inserted into the handle table when the flags are
false.- Round-trip consistency for primitive, collection, and nested values.
As per coding guidelines: "Prefer writing Rust unit tests over integration tests where possible" and "Always run
cargo test --libif you changed any Rust code."
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
baml_language/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (44)
baml_language/crates/baml_lsp_server/src/playground_server.rsbaml_language/crates/bex_project/Cargo.tomlbaml_language/crates/bex_project/src/lib.rsbaml_language/crates/bridge_cffi/src/ffi/callbacks.rsbaml_language/crates/bridge_cffi/src/ffi/functions.rsbaml_language/crates/bridge_cffi/src/ffi/handle.rsbaml_language/crates/bridge_cffi/src/ffi/mod.rsbaml_language/crates/bridge_cffi/src/ffi/objects.rsbaml_language/crates/bridge_cffi/src/lib.rsbaml_language/crates/bridge_ctypes/Cargo.tomlbaml_language/crates/bridge_ctypes/build.rsbaml_language/crates/bridge_ctypes/src/error.rsbaml_language/crates/bridge_ctypes/src/handle_table.rsbaml_language/crates/bridge_ctypes/src/lib.rsbaml_language/crates/bridge_ctypes/src/value_decode.rsbaml_language/crates/bridge_ctypes/src/value_encode.rsbaml_language/crates/bridge_ctypes/types/baml/cffi/v1/baml_inbound.protobaml_language/crates/bridge_ctypes/types/baml/cffi/v1/baml_object.protobaml_language/crates/bridge_ctypes/types/baml/cffi/v1/baml_object_methods.protobaml_language/crates/bridge_ctypes/types/baml/cffi/v1/baml_outbound.protobaml_language/crates/bridge_python/python_src/baml/cffi/v1/baml_inbound_pb2.pybaml_language/crates/bridge_python/python_src/baml/cffi/v1/baml_object_pb2.pybaml_language/crates/bridge_python/python_src/baml/cffi/v1/baml_outbound_pb2.pybaml_language/crates/bridge_python/python_src/baml_py/proto.pybaml_language/crates/bridge_python/src/handle.rsbaml_language/crates/bridge_python/src/lib.rsbaml_language/crates/bridge_python/src/runtime.rsbaml_language/crates/bridge_python/src/types/collector.rsbaml_language/crates/bridge_wasm/src/handle.rsbaml_language/crates/bridge_wasm/src/lib.rstypescript2/app-promptfiddle/src/playground/baml-lsp-worker.tstypescript2/package.jsontypescript2/pkg-playground/src/ExecutionPanel.tsxtypescript2/pkg-playground/src/ResultDisplay.tsxtypescript2/pkg-playground/src/ports/WebSocketRuntimePort.tstypescript2/pkg-playground/src/renderers/Media.tsxtypescript2/pkg-playground/src/renderers/PromptAst.tsxtypescript2/pkg-playground/src/renderers/registerBuiltins.tstypescript2/pkg-playground/src/worker-protocol.tstypescript2/pkg-proto/src/decode.tstypescript2/pkg-proto/src/encode.tstypescript2/pkg-proto/src/index.tstypescript2/pkg-proto/src/test/encode-decode.test.tstypescript2/pkg-proto/src/types.ts
💤 Files with no reviewable changes (4)
- baml_language/crates/bridge_ctypes/types/baml/cffi/v1/baml_object_methods.proto
- baml_language/crates/bridge_ctypes/build.rs
- baml_language/crates/bridge_ctypes/types/baml/cffi/v1/baml_object.proto
- baml_language/crates/bridge_python/python_src/baml/cffi/v1/baml_object_pb2.py
baml_language/crates/bridge_ctypes/types/baml/cffi/v1/baml_inbound.proto
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (2)
baml_language/crates/bridge_wasm/src/handle.rs (1)
46-53:f64 → u64cast for NaN/negative inputs silently produces key0.Already flagged in a previous review — the
key as u64cast converts NaN/negative → 0, which is the invalid sentinel. Construction succeeds but subsequent operations will fail. Consider validating or documenting this in the constructor body.baml_language/crates/bridge_ctypes/src/handle_table.rs (1)
122-146:RwLockpoisoning: all.unwrap()calls will cascade-panic if the lock is ever poisoned.Already flagged in a previous review. Since this is a global static used across all FFI bridges, a single panic while holding the lock will poison it permanently, causing every subsequent operation to panic as well. Consider using
.unwrap_or_else(|e| e.into_inner())to recover gracefully.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (6)
baml_language/.cargo/size-gate.tomlbaml_language/.ci/size-gate/wasm32-unknown-unknown.tomlbaml_language/crates/bridge_cffi/src/ffi/callbacks.rsbaml_language/crates/bridge_ctypes/src/handle_table.rsbaml_language/crates/bridge_python/src/types/collector.rsbaml_language/crates/bridge_wasm/src/handle.rs
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
baml_language/crates/baml_builtins_macros/src/codegen_native.rs (1)
523-555: 🧹 Nitpick | 🔵 TrivialRun
cargo test --libfor this Rust change.Please run the library tests as part of this update.
As per coding guidelines: "Always runcargo test --libif you changed any Rust code".baml_language/crates/bridge_ctypes/src/value_encode.rs (1)
18-137: 🧹 Nitpick | 🔵 TrivialAdd unit tests for the new handle/Media/PromptAst serialization branches.
Please add Rust unit tests covering the option-gated Media/PromptAst inline serialization and the handle-table fallback to prevent regressions in these paths. As per coding guidelines: Prefer writing Rust unit tests over integration tests where possible.
♻️ Duplicate comments (1)
baml_language/crates/bridge_ctypes/src/value_encode.rs (1)
227-273:⚠️ Potential issue | 🟡 MinorPreserve literal types instead of collapsing to AnyType.
Ty::Literal(_)is encoded asAnyType, which drops literal specificity for consumers. If the outbound proto supports a literal field type (e.g.,BamlFieldTypeLiteral), please encode it explicitly to avoid losing schema fidelity.#!/bin/bash set -euo pipefail rg -n "BamlFieldTypeLiteral|LiteralType" baml_language/crates/bridge_ctypes -g '*.proto' -g '*.rs'
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
baml_language/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (14)
baml_language/crates/baml_builtins/src/lib.rsbaml_language/crates/baml_builtins_macros/src/codegen_native.rsbaml_language/crates/bex_vm/src/native.rsbaml_language/crates/bridge_ctypes/src/error.rsbaml_language/crates/bridge_ctypes/src/handle_table.rsbaml_language/crates/bridge_ctypes/src/value_encode.rsbaml_language/crates/bridge_python/Cargo.tomlbaml_language/crates/bridge_python/src/types/collector.rsbaml_language/crates/bridge_wasm/src/handle.rstypescript2/app-promptfiddle/src/playground/baml-lsp-worker.tstypescript2/pkg-playground/src/ResultDisplay.tsxtypescript2/pkg-playground/src/ports/WebSocketRuntimePort.tstypescript2/pkg-proto/src/decode.tstypescript2/pkg-proto/src/test/encode-decode.test.ts
ea97fbb to
a786957
Compare
There was a problem hiding this comment.
Actionable comments posted: 9
♻️ Duplicate comments (3)
typescript2/app-promptfiddle/src/playground/baml-lsp-worker.ts (1)
483-483:⚠️ Potential issue | 🟡 Minor
JSON.stringifywill throw on bigint values — bigint replacer still missing
decodeCallResultcan return bigint values (from protoint64fields).JSON.stringifythrows aTypeErroron any bigint it encounters. The fix was previously suggested but has not been applied.🛡️ Proposed fix
- const result = JSON.stringify(decoded, null, 2); + const result = JSON.stringify( + decoded, + (_, v) => (typeof v === 'bigint' ? v.toString() : v), + 2, + );baml_language/crates/bridge_ctypes/src/handle_table.rs (1)
1-3: Run Rust unit tests for this change.
Please runcargo test --lib.As per coding guidelines: Always run
cargo test --libif you changed any Rust code.baml_language/crates/bridge_ctypes/src/value_encode.rs (1)
227-262:⚠️ Potential issue | 🟡 MinorLiteral type metadata is still dropped to
AnyType.
If the outbound proto still supportsBamlFieldTypeLiteral, consider preserving literal values instead of collapsing toAnyType.#!/bin/bash # Verify whether BamlFieldTypeLiteral exists in proto/generated code rg -n "BamlFieldTypeLiteral" --type proto --type rust # Inspect Ty::Literal to understand expected literal variants rg -n "Ty::Literal" --type rust -C 2
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
baml_language/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (11)
baml_language/crates/bridge_ctypes/src/error.rsbaml_language/crates/bridge_ctypes/src/handle_table.rsbaml_language/crates/bridge_ctypes/src/value_encode.rsbaml_language/crates/bridge_python/Cargo.tomlbaml_language/crates/bridge_python/src/types/collector.rsbaml_language/crates/bridge_wasm/src/handle.rstypescript2/app-promptfiddle/src/playground/baml-lsp-worker.tstypescript2/pkg-playground/src/ResultDisplay.tsxtypescript2/pkg-playground/src/ports/WebSocketRuntimePort.tstypescript2/pkg-proto/src/decode.tstypescript2/pkg-proto/src/test/encode-decode.test.ts
- Proto: InboundValue reserved 1; MediaTypeEnum add MEDIA_TYPE_UNSPECIFIED - bridge_wasm: fix BamlHandle docstrings (key, handle_type) - baml-lsp-worker: clear liveHandles when new call returns 0 handles - ResultDisplay: use BAML_TYPE_KEY, simplify isComplex - WebSocketRuntimePort: emit callFunctionError on decode failure - pkg-proto decode: exhaustive switch defaults, tryParseJson metadata, handle.key ?? 0, MEDIA_TYPE_UNSPECIFIED in MEDIA_TYPE_NAMES - encode-decode.test: use BamlHandleType.FUNCTION_REF instead of magic 5 Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
typescript2/pkg-proto/src/decode.ts (1)
130-221: 🛠️ Refactor suggestion | 🟠 Major
deserializeValue—defaultbranch silently returnsnullfor unrecognized$casevariants.Line 218–219: when a new outbound value variant is added to the proto, the
defaultbranch returnsnullwithout logging or throwing. Unlike the media/promptAst helpers above, there's no compile-time exhaustiveness check (const _exhaustive: never) here, so new proto variants will silently disappear.Consider adding an exhaustive check (same pattern as the other deserializers) to get compile-time breakage when new variants are introduced.
Proposed fix
- default: - return null; + default: { + const _exhaustive: never = holder.value; + return null; + }typescript2/pkg-proto/src/test/encode-decode.test.ts (1)
95-257: 🛠️ Refactor suggestion | 🟠 MajorNo test coverage for
mediaValueandpromptAstValuedeserialization.
decode.tsintroduces substantial new logic indeserializeMedia,deserializePromptAstSimple, anddeserializePromptAst, but these code paths have no corresponding tests. Consider adding tests that:
- Encode a
mediaValuewith url/base64/file variants and verify the decodedBamlJsMediashape.- Encode a
promptAstValuewith simple/message/multiple variants (including nested structures and metadata) and verify the decodedBamlJsPromptAstshape.- Verify that the
tryParseJsonfallback works whenmetadataAsJsonis invalid JSON.These are the most complex new deserializers in this PR and would benefit from dedicated encode-decode round-trip tests.
typescript2/app-promptfiddle/src/playground/baml-lsp-worker.ts (2)
487-493:⚠️ Potential issue | 🟡 MinorHandles created before
decodeCallResultthrows are leaked on the error path.If
decodeCallResult(orJSON.stringify) throws after the callback has already created someBamlHandleinstances, those handles sit in the localhandlesarray but are never freed — they don't reachliveHandlesand.free()is never called.🛡️ Proposed fix: free partially-created handles in catch
} catch (e) { + for (const h of handles) h.free(); postOut({ type: "callFunctionError", id: msg.id, error: e instanceof Error ? e.message : String(e), }); }Note:
handlesneeds to be declared outside thetryblock for this to work. Move the declaration to before thetry:+ const handles: BamlHandle[] = []; try { const resultBytes = await runtime.callFunction( msg.id, msg.project, msg.name, msg.argsProto, ); const bytes = new Uint8Array(resultBytes); - const handles: BamlHandle[] = []; const decoded = decodeCallResult(bytes, (key, handleType, typeName) => {
54-70:⚠️ Potential issue | 🟠 Major
dispose()does not free handles inliveHandles, leaking WASM memory.When the worker is disposed, all
BamlHandleobjects stored inliveHandlesshould be explicitly freed — same pattern asclearHandles. Without this, outstanding WASM allocations are never reclaimed deterministically.🔒 Proposed fix: free all live handles on dispose
function dispose(): void { if (disposed) return; disposed = true; + // Free all outstanding WASM handles + for (const handles of liveHandles.values()) { + for (const h of handles) h.free(); + } + liveHandles.clear(); // Resolve any pending env requests so awaiting callers don't hang for (const resolve of pendingEnvResolvers.values()) {
♻️ Duplicate comments (4)
typescript2/pkg-proto/src/decode.ts (2)
56-75: Exhaustive switch and null guard addressed from prior feedback.The
defaultbranch withconst _exhaustive: never = m.valueat Line 71 ensures compile-time safety if the proto oneof gains new variants, and the runtime fallback is reasonable.
202-210: Handle keyundefinedguard addressed from prior feedback.Line 205's
BigInt(handle.key ?? 0)correctly defaults an unset key to0n, preventing aTypeErrorfromBigInt(undefined).typescript2/pkg-proto/src/test/encode-decode.test.ts (1)
240-257: Handle wrapping test uses symbolic enum constant — past feedback addressed.Uses
BamlHandleType.FUNCTION_REFinstead of a magic number, and properly asserts all three callback arguments (key,handleType,typeName) plus the final decoded shape. Good coverage of this path.baml_language/crates/bridge_ctypes/src/handle_table.rs (1)
1-2: Reminder: runcargo test --libfor this Rust change.As per coding guidelines: Always run
cargo test --libif you changed any Rust code.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (9)
baml_language/crates/bridge_ctypes/src/handle_table.rsbaml_language/crates/bridge_ctypes/types/baml/cffi/v1/baml_inbound.protobaml_language/crates/bridge_ctypes/types/baml/cffi/v1/baml_outbound.protobaml_language/crates/bridge_wasm/src/handle.rstypescript2/app-promptfiddle/src/playground/baml-lsp-worker.tstypescript2/pkg-playground/src/ResultDisplay.tsxtypescript2/pkg-playground/src/ports/WebSocketRuntimePort.tstypescript2/pkg-proto/src/decode.tstypescript2/pkg-proto/src/test/encode-decode.test.ts
- Use arc_prompt_ast_to_proto / arc_prompt_ast_simple_to_proto so .map(fn) satisfies redundant-closure review (lines 188, 217) - Map Ty::Literal to FieldType::LiteralType(BamlFieldTypeLiteral) instead of AnyType; add literal_to_field_type_literal and baml_type dep Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
This PR introduces a handle-based CFFI model for opaque values (Media, PromptAst, Resources, FunctionRef, etc.) and wire-friendly serialization so the playground (WASM ↔ TypeScript) can display results without a shared process. The approach was inspired by
.humanlayer/tasks/vbv-wasm-events/2026-02-21-plan.md(WASM event store / injectable sink); we adapted the idea to the CFFI boundary: injectable encoding options (wire vs in-process) and a global handle table instead of per-object method calls.Changes
bridge_ctypes): Global table storingHandle,Resource,FunctionRef, andAdt(Media, PromptAst, Collector, Type). Opaque values are keyed byu64and can be round-tripped asBamlHandle(key, handle_type).InboundValuecan carryBamlHandle; decode useshandle_table.resolve(key)to produceBexExternalValue.HandleTableOptions—for_wire()(playground/WASM): serialize Media and PromptAst inline asBamlValueMedia/BamlValuePromptAst;for_in_process()(Python): keep them as handles only.BamlHandle+BamlHandleTypein inbound; first-classBamlValueMediaandBamlValuePromptAstin outbound; removedbaml_object.protoandbaml_object_methods.proto.clone_handle/release_handleexposed from C, Python, and WASM.MediaandPromptAstrenderers in TS;pkg-protodecode/encode for wire types; worker and ExecutionPanel updated to use the new result shape.bridge_ctypesnow depends only onbex_project;Handleis re-exported frombex_project.Flow diagram
flowchart TB subgraph Host["Host (Python / TS)"] A[call_function args] B[result JSON / proto] end subgraph CFFI["CFFI boundary"] C[call_function] D[callbacks / return] end subgraph Encode["Encode (outbound)"] E[BexExternalValue] F{HandleTableOptions} G[for_wire] H[for_in_process] G --> I[BamlValueMedia / BamlValuePromptAst inline] H --> J[insert into HandleTable → BamlHandle] E --> F F --> G F --> H end subgraph Decode["Decode (inbound)"] K[InboundValue] L{BamlHandle?} L -->|yes| M[handle_table.resolve] L -->|no| N[primitive / list / map / class] M --> O[BexExternalValue] N --> O K --> L end subgraph Table["Handle table (global)"] T[(key → HandleTableValue)] T --> P[Handle / Resource / FunctionRef / Adt] end A --> K Decode --> C C --> E Encode --> D D --> B J --> T M --> TInbound (host → engine): Host sends
InboundValue(primitives, containers, orBamlHandle). Decode resolves handles via the global handle table and producesBexExternalValuefor the engine.Outbound (engine → host): Engine produces
BexExternalValue. Encode usesHandleTableOptions:clone_handle/release_handlefor lifecycle.Testing
cargo test --libinbaml_language(and relevant crates).pkg-protoencode/decode tests; playground run flow if available.Summary by CodeRabbit
New Features
Improvements