feat: upstream SDK codegen and runtime foundations#4087
Conversation
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
⏭️ 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):
|
|
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:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (3)
📒 Files selected for processing (7)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughThis change adds ChangesTyped bridge and codegen flow
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Host
participant Bridge
participant BexEngine
participant VM
Host->>Bridge: send value with sparse type metadata
Bridge->>BexEngine: decode typed value and selected union arm
BexEngine->>VM: coerce contextual payload and execute call
VM->>BexEngine: return typed union or host result
BexEngine->>Bridge: encode canonical selected option index
Bridge->>Host: deliver converted value
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 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 |
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 (1)
baml_language/sdks/rust/bridge_rust/src/baml_value.rs (1)
219-223: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftPopulate collection type descriptors instead of always sending
None.These implementations know
T,K, andV, but every Rust collection decodes with fallback metadata. For example,Vec<String>becomes an array typed with the scalar fallback, while non-string maps default to string keys.Expose a wire-type descriptor through the value/key traits and emit
Some(...)for all three implementations. Add unit coverage for empty collections.As per coding guidelines, "Prefer writing Rust unit tests over integration tests where possible."
Also applies to: 260-271, 296-307
🤖 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 `@baml_language/sdks/rust/bridge_rust/src/baml_value.rs` around lines 219 - 223, The Rust collection implementations of __BamlValuePrivate for Vec, HashMap, and BTreeMap currently send item_type/key_type/value_type as None. Extend the relevant value/key traits with wire-type descriptor support, use T/K/V to emit Some(...) descriptors in all three implementations, and add Rust unit tests covering empty collections and their descriptors.Source: Coding guidelines
🤖 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/bex_engine/src/conversion.rs`:
- Around line 919-928: Update the BexExternalValue::Union branch in
convert_external_to_vm_value_with_ty to validate metadata.selected_option
against the declared expected_ty union and verify that value inhabits the
selected arm before materializing it. Reuse the existing membership and payload
validation logic from coerce_arg_to_declared_type, rejecting malformed arms or
callable signatures before the recursive conversion.
- Around line 2239-2304: The union coercion logic does not recognize function
arms or callable host values. Extend runtime_ty_structurally_equal to compare
RuntimeTy::Function signatures recursively, and update value_matches_type to
accept the HostValue/FunctionRef representations for function types. Add a
regression test covering a host-selected callback in a union and preserve
existing matching behavior for other arms.
- Around line 2033-2067: Update both shared and schema-aware host-return
validators to mirror the matching rules used by runtime_ty_compatible: compare
float literals with float_literal_matches, recursively validate list and map
element/key/value descriptors even when containers are empty, and require the
returned media kind to match the declared type. Preserve exact literal checks
for other scalar values so mismatched literals, empty string arrays, and audio
returned for image declarations are rejected.
In `@baml_language/crates/bridge_ctypes/src/ty_decode.rs`:
- Around line 256-261: Update the BigintValue branch in the literal decoding
logic to reject decimal strings exceeding the existing value-level bigint length
limit before calling parse_bytes or formatting the input. Return a length-only
CtypesError for oversized values, while preserving the current invalid-format
error for bounded inputs.
In `@baml_language/crates/bridge_ctypes/src/value_decode.rs`:
- Around line 95-129: Update convert_union_variant and the corresponding
list/map decoding paths to validate each decoded payload recursively against its
wire-declared RuntimeTy, not only union membership. Ensure selected_type,
item_type, key_type, and value_type reject mismatched values before constructing
BexExternalValue containers or unions, while preserving valid nested values. Add
Rust unit tests covering mismatched union, list, and map payloads.
---
Outside diff comments:
In `@baml_language/sdks/rust/bridge_rust/src/baml_value.rs`:
- Around line 219-223: The Rust collection implementations of __BamlValuePrivate
for Vec, HashMap, and BTreeMap currently send item_type/key_type/value_type as
None. Extend the relevant value/key traits with wire-type descriptor support,
use T/K/V to emit Some(...) descriptors in all three implementations, and add
Rust unit tests covering empty collections and their descriptors.
🪄 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
Run ID: e7abb70f-b98f-4744-aab8-627f95646797
⛔ Files ignored due to path filters (2)
baml_language/sdks/typescript/bridge_typescript/dist/proto/baml_cffi.d.tsis excluded by!**/dist/**baml_language/sdks/typescript/bridge_typescript/dist/proto/baml_cffi.jsis excluded by!**/dist/**
📒 Files selected for processing (31)
baml_language/crates/baml_project/src/client_codegen.rsbaml_language/crates/bex_engine/src/conversion.rsbaml_language/crates/bex_engine/src/lib.rsbaml_language/crates/bex_engine/tests/host_value_callable.rsbaml_language/crates/bex_external_types/src/host_return.rsbaml_language/crates/bex_external_types/src/lib.rsbaml_language/crates/bridge_cffi/src/ffi/host_value.rsbaml_language/crates/bridge_ctypes/README.mdbaml_language/crates/bridge_ctypes/src/error.rsbaml_language/crates/bridge_ctypes/src/ty_decode.rsbaml_language/crates/bridge_ctypes/src/ty_encode.rsbaml_language/crates/bridge_ctypes/src/value_decode.rsbaml_language/crates/bridge_ctypes/src/value_encode.rsbaml_language/crates/bridge_ctypes/types/baml_bridge/cffi/v1/baml_inbound.protobaml_language/crates/bridge_ctypes/types/baml_bridge/cffi/v1/baml_outbound.protobaml_language/crates/bridge_ctypes/types/baml_bridge/cffi/v1/baml_type.protobaml_language/crates/sys_native/src/host_dispatch.rsbaml_language/sdks/cpp/bridge_cpp/pb/baml_bridge/cffi/v1/baml_inbound.pb.ccbaml_language/sdks/cpp/bridge_cpp/pb/baml_bridge/cffi/v1/baml_inbound.pb.hbaml_language/sdks/cpp/bridge_cpp/pb/baml_bridge/cffi/v1/baml_outbound.pb.ccbaml_language/sdks/cpp/bridge_cpp/pb/baml_bridge/cffi/v1/baml_outbound.pb.hbaml_language/sdks/python/src/baml_bridge/cffi/v1/baml_inbound_pb2.pybaml_language/sdks/python/src/baml_bridge/cffi/v1/baml_inbound_pb2.pyibaml_language/sdks/python/src/baml_bridge/cffi/v1/baml_outbound_pb2.pybaml_language/sdks/python/src/baml_bridge/cffi/v1/baml_outbound_pb2.pyibaml_language/sdks/rust/bridge_rust/src/baml_value.rsbaml_language/sdks/rust/bridge_rust/src/wire/baml_bridge.cffi.v1.rsbaml_language/sdks/typescript/bridge_typescript/typescript_src/proto/baml_cffi.d.tsbaml_language/sdks/typescript/bridge_typescript/typescript_src/proto/baml_cffi.jstypescript2/pkg-proto/src/encode.tstypescript2/pkg-proto/src/test/encode-decode.test.ts
edf8aad to
de6eadb
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/bridge_ctypes/src/value_encode.rs`:
- Around line 366-387: The selected option lookup in selected_union_option_index
currently uses strict RuntimeTy equality; replace the members.iter().position
comparison with the existing structural union-member equality helper used by
selected_arm_equal/runtime_ty_structurally_equal. Preserve the current
not-member error and u32 index conversion behavior.
🪄 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
Run ID: cb915954-7a05-482f-a70c-affbdaf2b612
⛔ Files ignored due to path filters (2)
baml_language/sdks/typescript/bridge_typescript/dist/proto/baml_cffi.d.tsis excluded by!**/dist/**baml_language/sdks/typescript/bridge_typescript/dist/proto/baml_cffi.jsis excluded by!**/dist/**
📒 Files selected for processing (32)
baml_language/crates/baml_project/src/client_codegen.rsbaml_language/crates/bex_engine/src/conversion.rsbaml_language/crates/bex_engine/src/lib.rsbaml_language/crates/bex_engine/tests/host_value_callable.rsbaml_language/crates/bex_external_types/src/bex_external_value.rsbaml_language/crates/bex_external_types/src/host_return.rsbaml_language/crates/bex_external_types/src/lib.rsbaml_language/crates/bridge_cffi/src/ffi/host_value.rsbaml_language/crates/bridge_ctypes/README.mdbaml_language/crates/bridge_ctypes/src/error.rsbaml_language/crates/bridge_ctypes/src/ty_decode.rsbaml_language/crates/bridge_ctypes/src/ty_encode.rsbaml_language/crates/bridge_ctypes/src/value_decode.rsbaml_language/crates/bridge_ctypes/src/value_encode.rsbaml_language/crates/bridge_ctypes/types/baml_bridge/cffi/v1/baml_inbound.protobaml_language/crates/bridge_ctypes/types/baml_bridge/cffi/v1/baml_outbound.protobaml_language/crates/bridge_ctypes/types/baml_bridge/cffi/v1/baml_type.protobaml_language/crates/sys_native/src/host_dispatch.rsbaml_language/sdks/cpp/bridge_cpp/pb/baml_bridge/cffi/v1/baml_inbound.pb.ccbaml_language/sdks/cpp/bridge_cpp/pb/baml_bridge/cffi/v1/baml_inbound.pb.hbaml_language/sdks/cpp/bridge_cpp/pb/baml_bridge/cffi/v1/baml_outbound.pb.ccbaml_language/sdks/cpp/bridge_cpp/pb/baml_bridge/cffi/v1/baml_outbound.pb.hbaml_language/sdks/python/src/baml_bridge/cffi/v1/baml_inbound_pb2.pybaml_language/sdks/python/src/baml_bridge/cffi/v1/baml_inbound_pb2.pyibaml_language/sdks/python/src/baml_bridge/cffi/v1/baml_outbound_pb2.pybaml_language/sdks/python/src/baml_bridge/cffi/v1/baml_outbound_pb2.pyibaml_language/sdks/rust/bridge_rust/src/baml_value.rsbaml_language/sdks/rust/bridge_rust/src/wire/baml_bridge.cffi.v1.rsbaml_language/sdks/typescript/bridge_typescript/typescript_src/proto/baml_cffi.d.tsbaml_language/sdks/typescript/bridge_typescript/typescript_src/proto/baml_cffi.jstypescript2/pkg-proto/src/encode.tstypescript2/pkg-proto/src/test/encode-decode.test.ts
🚧 Files skipped from review as they are similar to previous changes (20)
- baml_language/crates/bridge_ctypes/src/error.rs
- baml_language/crates/bridge_ctypes/types/baml_bridge/cffi/v1/baml_type.proto
- baml_language/crates/bridge_ctypes/types/baml_bridge/cffi/v1/baml_outbound.proto
- baml_language/sdks/python/src/baml_bridge/cffi/v1/baml_outbound_pb2.pyi
- baml_language/crates/bex_external_types/src/lib.rs
- baml_language/sdks/python/src/baml_bridge/cffi/v1/baml_inbound_pb2.py
- baml_language/crates/bex_engine/tests/host_value_callable.rs
- typescript2/pkg-proto/src/encode.ts
- baml_language/crates/bridge_ctypes/src/ty_encode.rs
- baml_language/crates/bridge_cffi/src/ffi/host_value.rs
- baml_language/sdks/rust/bridge_rust/src/baml_value.rs
- baml_language/crates/bridge_ctypes/src/ty_decode.rs
- baml_language/crates/sys_native/src/host_dispatch.rs
- baml_language/crates/bex_external_types/src/host_return.rs
- baml_language/crates/baml_project/src/client_codegen.rs
- baml_language/sdks/python/src/baml_bridge/cffi/v1/baml_outbound_pb2.py
- baml_language/crates/bex_engine/src/lib.rs
- baml_language/sdks/cpp/bridge_cpp/pb/baml_bridge/cffi/v1/baml_outbound.pb.h
- baml_language/crates/bex_engine/src/conversion.rs
- baml_language/sdks/cpp/bridge_cpp/pb/baml_bridge/cffi/v1/baml_outbound.pb.cc
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/bex_external_types/src/runtime_ty_identity.rs`:
- Around line 72-79: Update the T::Union branch of runtime_ty_structurally_equal
to require every member in both unions to have a structurally equal counterpart
in the other union, while retaining the length check. Ensure duplicate-member
cases produce the same result regardless of operand order.
🪄 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
Run ID: 9741d196-0a3c-4943-b752-a4bffa9d463e
📒 Files selected for processing (7)
baml_language/crates/bex_engine/src/conversion.rsbaml_language/crates/bex_external_types/src/lib.rsbaml_language/crates/bex_external_types/src/runtime_ty_identity.rsbaml_language/crates/bex_project/src/lib.rsbaml_language/crates/bridge_ctypes/src/error.rsbaml_language/crates/bridge_ctypes/src/ty_decode.rsbaml_language/crates/bridge_ctypes/src/value_encode.rs
🚧 Files skipped from review as they are similar to previous changes (3)
- baml_language/crates/bridge_ctypes/src/ty_decode.rs
- baml_language/crates/bridge_ctypes/src/value_encode.rs
- baml_language/crates/bex_engine/src/conversion.rs
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
baml_language/sdks/typescript/bridge_typescript/typescript_src/proto.ts (1)
243-263: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winMissing
fqnguard for generic class instances — inconsistent with sibling encode paths.The non-generic class branch (Lines 219-233) and
setInboundTypedThrowValue(Lines 913-930) both guard withif (fqn) { ...; return; }, falling back to a safer encoding when the class isn't registered in the type map. This generic-instance branch skips that guard and unconditionally returnsiv.valueType = { classTy: { name: fqn, typeArgs } }even whenfqnis falsy, producing a malformed wire message (empty/undefined class name) instead of falling through to the plain-map encoding used elsewhere for this exact situation.🐛 Proposed fix
if (isClassInstance) { const params = genericParamNames(value); if (params) { const fqn = getTypeMap().jsTypeToBamlType((value as object).constructor); + if (fqn) { const userTypes = (value as { $types?: Record<string, BamlType> }).$types; const typeArgs = params.map((p) => lowerTypeToWireTy(userTypes?.[p])); const classFields: baml_bridge.cffi.v1.IInboundMapEntry[] = []; for (const [k, v] of Object.entries(value)) { if (typeof v === 'function') continue; if (k === '$types') continue; const childVal: baml_bridge.cffi.v1.IInboundValue = {}; setInboundValue(childVal, v, ctx); classFields.push({ stringKey: k, value: childVal }); } iv.valueType = { classTy: { name: fqn, typeArgs } }; iv.classValue = { fields: classFields }; return; + } } }🤖 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 `@baml_language/sdks/typescript/bridge_typescript/typescript_src/proto.ts` around lines 243 - 263, Guard the generic class-instance encoding in the isClassInstance branch with the resolved fqn from getTypeMap().jsTypeToBamlType, matching the non-generic branch and setInboundTypedThrowValue. Only assign classTy/classValue and return when fqn is present; otherwise fall through to the existing plain-map encoding.
🤖 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/bridge_ctypes/types/baml_bridge/cffi/v1/baml_inbound.proto`:
- Around line 66-69: Update the InboundClassValue message to reserve tag 3 in
addition to the existing reserved tag 1, preserving field 2 and preventing reuse
of the removed class_ty field number.
---
Outside diff comments:
In `@baml_language/sdks/typescript/bridge_typescript/typescript_src/proto.ts`:
- Around line 243-263: Guard the generic class-instance encoding in the
isClassInstance branch with the resolved fqn from getTypeMap().jsTypeToBamlType,
matching the non-generic branch and setInboundTypedThrowValue. Only assign
classTy/classValue and return when fqn is present; otherwise fall through to the
existing plain-map encoding.
🪄 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
Run ID: 812d5a58-c349-4b88-8624-f9b8b9fa7052
⛔ Files ignored due to path filters (5)
baml_language/sdks/typescript/bridge_typescript/dist/proto.d.ts.mapis excluded by!**/dist/**,!**/*.mapbaml_language/sdks/typescript/bridge_typescript/dist/proto.jsis excluded by!**/dist/**baml_language/sdks/typescript/bridge_typescript/dist/proto.js.mapis excluded by!**/dist/**,!**/*.mapbaml_language/sdks/typescript/bridge_typescript/dist/proto/baml_cffi.d.tsis excluded by!**/dist/**baml_language/sdks/typescript/bridge_typescript/dist/proto/baml_cffi.jsis excluded by!**/dist/**
📒 Files selected for processing (37)
baml_language/crates/bex_engine/src/conversion.rsbaml_language/crates/bex_engine/src/lib.rsbaml_language/crates/bex_engine/tests/generics_explicit.rsbaml_language/crates/bex_engine/tests/host_value_callable.rsbaml_language/crates/bex_external_types/src/bex_external_value.rsbaml_language/crates/bex_external_types/src/host_return.rsbaml_language/crates/bex_external_types/src/runtime_ty_identity.rsbaml_language/crates/bridge_cffi/src/ffi/host_value.rsbaml_language/crates/bridge_ctypes/src/value_decode.rsbaml_language/crates/bridge_ctypes/src/value_encode.rsbaml_language/crates/bridge_ctypes/types/baml_bridge/cffi/v1/baml_inbound.protobaml_language/crates/bridge_ctypes/types/baml_bridge/cffi/v1/baml_outbound.protobaml_language/crates/bridge_ctypes/types/baml_bridge/cffi/v1/baml_type.protobaml_language/crates/bridge_wasm/tests/host_callable.rsbaml_language/crates/sys_wasm/src/host_value.rsbaml_language/sdk_tests/crates/rust/type_shapes/customizable/roundtrip_tests/test_primitives.rsbaml_language/sdks/cpp/bridge_cpp/include/baml/detail/host_value.hbaml_language/sdks/cpp/bridge_cpp/pb/baml_bridge/cffi/v1/baml_inbound.pb.ccbaml_language/sdks/cpp/bridge_cpp/pb/baml_bridge/cffi/v1/baml_inbound.pb.hbaml_language/sdks/cpp/bridge_cpp/pb/baml_bridge/cffi/v1/baml_outbound.pb.ccbaml_language/sdks/cpp/bridge_cpp/pb/baml_bridge/cffi/v1/baml_outbound.pb.hbaml_language/sdks/cpp/sdkgen_cpp/src/lib.rsbaml_language/sdks/python/rust/bridge_python/src/host_value.rsbaml_language/sdks/python/src/baml_bridge/cffi/v1/baml_inbound_pb2.pybaml_language/sdks/python/src/baml_bridge/cffi/v1/baml_inbound_pb2.pyibaml_language/sdks/python/src/baml_bridge/cffi/v1/baml_outbound_pb2.pybaml_language/sdks/python/src/baml_bridge/cffi/v1/baml_outbound_pb2.pyibaml_language/sdks/python/src/baml_bridge/proto.pybaml_language/sdks/python/tests/test_proto_generics.pybaml_language/sdks/rust/bridge_rust/src/baml_value.rsbaml_language/sdks/rust/bridge_rust/src/encode.rsbaml_language/sdks/rust/bridge_rust/src/wire/baml_bridge.cffi.v1.rsbaml_language/sdks/typescript/bridge_typescript/typescript_src/proto.tsbaml_language/sdks/typescript/bridge_typescript/typescript_src/proto/baml_cffi.d.tsbaml_language/sdks/typescript/bridge_typescript/typescript_src/proto/baml_cffi.jstypescript2/pkg-proto/src/encode.tstypescript2/pkg-proto/src/test/encode-decode.test.ts
🚧 Files skipped from review as they are similar to previous changes (9)
- baml_language/crates/bridge_ctypes/types/baml_bridge/cffi/v1/baml_type.proto
- baml_language/sdks/python/src/baml_bridge/cffi/v1/baml_inbound_pb2.py
- baml_language/sdks/python/src/baml_bridge/cffi/v1/baml_outbound_pb2.py
- baml_language/crates/bex_engine/tests/host_value_callable.rs
- baml_language/crates/bex_external_types/src/host_return.rs
- baml_language/crates/bridge_cffi/src/ffi/host_value.rs
- baml_language/crates/bex_engine/src/lib.rs
- baml_language/crates/bex_external_types/src/runtime_ty_identity.rs
- baml_language/crates/bridge_ctypes/src/value_encode.rs
|
Final validation for The final GitHub Actions matrix has 35 successful jobs and no non-Go implementation failures. In particular, core Cargo tests pass on Linux/macOS/Windows/WASM; C++, Python Pydantic 2, Rust, TypeScript, and TypeScript Web SDK jobs pass on every configured platform; pre-commit, docs, snapshots, webview, and size gates pass. The only intentional failures are the upstream-only boundary documented in this PR:
Those Go files remain deliberately excluded because this is the shared upstream foundations PR; the Go bridge/generator/runtime work stays in its downstream PR. Additional local focused verification on the final implementation included:
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
Why this PR exists
This PR extracts the compiler, runtime, bridge, and wire-format work shared by every host SDK from the Go bridge work. It intentionally does not add a Go generator/runtime, Go tests, Go release configuration, or generated Go bindings.
The central problem is that a payload's runtime shape does not always identify its BAML type. That matters at host boundaries, especially callbacks, because the engine must distinguish the exact type chosen by the producer and validate it against the declared BAML signature.
Both of these host values are ambiguous from payload shape alone:
The final design uses one sparse type annotation on any inbound node that needs it, recursively threads contextual types through unannotated descendants, and uses one canonical selected-arm discriminator for outbound unions. The same PR also lowers class-method
Selfbefore SDK generation, centralizes boundary type matching, and completes the shared host-callable dispatch path.Surface area
Selfto the owning concrete or generic class before language generators see ittypevalues, sparse inbound annotations, and canonical selected union arms without engine-only typesInboundValue.value_type; remove typed envelopes and container/class-local descriptorsselected_option_index; remove the redundantselected_typefieldbaml.json.jsonSuggested review order:
baml_inbound.protoandbaml_outbound.protofor the contract.bex_external_typesfor the shared value/type representation.bex_engine/src/conversion.rsfor recursive contextual materialization.baml_project/src/client_codegen.rsforSelflowering.sys_native,sys_wasm, andbridge_cffifor callback dispatch/lifecycle.1. Inbound: one sparse annotation on every value node
Final wire shape
This replaces all earlier, redundant type channels:
InboundTypedValue/InboundValue.typed_value;InboundListValue.item_type;InboundMapValue.key_typeandvalue_type;InboundClassValue.class_ty.value_typealways describes that node. It is never a second copy of the enclosing union. The normal form is exact; the one deliberate partial form is a generic class annotation that carries nominal class identity while omitting host-erased type arguments, described below.Semantics
For each inbound node:
value_typeis present, require it to be assignable to that context.value_type.Annotations are therefore optional and sparse. Producers do not need an expensive pass that serializes a complete recursive type tree for every value. They emit a descriptor only where payload evidence and context cannot make the selection uniquely.
Typical annotation sites are:
The canonical encoding is the least-annotated valid encoding.
Exact nested example
For a declared argument
(int[] | string[])[], only the ambiguous empty child needs a hint:The outer list is known from the function parameter. The second child is known from its string payload. Serializing either type would be redundant. The first child's empty payload matches both union members, so its exact
int[]annotation is necessary.Literal example
For
"draft" | string, the raw string payload matches both arms. This encoding selects the literal:Without the annotation, conversion fails as ambiguous rather than silently choosing an arm. If the contextual type were only
string, the same annotation remains valid because the literal is assignable to the broad primitive.Class and generic behavior
A class field map does not need
class_tyembedded inInboundClassValue. The declared class type becomes the context for the class node, and the class schema supplies each field's recursive context. A sparse node-level annotation is still available when there is no unique class context or when a concrete generic instantiation must be stated.For example, an unannotated payload for
Box<T>can use the realizedBox<string>parameter context. An explicitBox<int>annotation in that same slot is rejected as conflicting instead of being ignored. Context-free throws retain their exact class annotation as a transient carrier until materialization, which preserves class identity without inventing a container-specific field.TypeScript and Python have one important erased-generic case: the host can know that an object is a
Boxwhile not knowing its concreteT. After removingInboundClassValue.class_ty, dropping that name would also drop the only nominal discriminator. These producers therefore encodevalue_type = Boxwith zero type arguments as a sparse nominal hint. The engine may refine that hint against exactly one contextualBox<...>type, but it will not use it to choose betweenBox<int> | Box<string>; that genuinely ambiguous union requires concrete arguments. Generated non-generic class instances carry their exact class annotation for the same nominal-union reason.Plain object literals remain unannotated. When selecting a class union arm, the engine compares their keys, required/optional fields, aliases, and recursively visible payload shapes against the loaded class definitions. Distinct structural arms such as
CardPayment | WirePaymentneed no annotation; overlapping class shapes still do.Reflected types remain values
ty_valueis separate fromvalue_type:value_typedescribes an inbound node;ty_valueis the node's payload when a BAMLtypevalue itself is being passed as data.This keeps reflection round-trippable without reducing type values to display strings.
2. Outbound unions: index is the only selected-arm identity
Final wire shape
The selected type is derived canonically:
For an empty
int[]selected fromint[] | string[], the wire representation is conceptually:selected_typewas removed because it could disagree with the index. The optional index preserves member order and equivalent/duplicate-arm position, while presence distinguishes arm zero from a missing discriminator.value_option_nameremains display-only and is not type authority.The nested value's extracted type is useful for compatibility validation and best-effort inference, but cannot identify the selected arm. A string payload cannot distinguish literal
"draft"from broadstring; an empty list cannot distinguishint[]fromstring[].Decoders validate that the index is in bounds and that the nested payload is compatible with
self_type.options[index].3.
Selfis lowered once, in the compilerClass methods previously exposed
Selfin SDK-facing IR. That would require every language generator to reconstruct the enclosing class and recursively substitute through optionals, lists, maps, unions, and generic arguments.The compiler now performs that lowering while it owns the authoritative class context:
The generator sees:
This applies to instance and static methods. The instance receiver remains excluded from public SDK arguments.
4. Shared boundary matching
The bridge and engine now use common runtime identity/compatibility rules instead of several payload-shape checks.
typety_valueasBamlTybaml.json.jsonThe accepted JSON algebra is
null | bool | int | finite float | string | list<json> | map<string, json>. Big integer type literals also get pre-parse length and post-parse bit-width limits.5. Host-callable boundary
When BAML invokes a registered host function, arguments are externalized using the realized callable signature, not just payload inference:
The shared dispatch sequence is:
HostClosureparameter types.returnsorthrowstype before resuming the VM.Contract mismatches are host contract violations. Pointer/protobuf/transport failures remain bridge failures, so callers can distinguish an off-contract callback result from a broken protocol.
The lifecycle work also covers cancellation eviction, registry teardown, malformed pointer and
is_errorchecks, GC-safe ownership of return/throw types across suspension, and deterministic release of late or accepted host-error handles. After merging current canary, the new C++ host-callable producer and the WASM host path use the same sparse node annotation.6. SDK producer behavior
Generated class instances and user-defined typed wrappers can opt into
value_type; ordinary arrays and plain objects remain minimally encoded. A generated class emits its nominal identity, plus concrete generic arguments only when the host actually has them.For example, the TypeScript encoder conceptually emits:
Plain arrays and objects do not recursively serialize type descriptors. Only an ambiguous child needs a wrapper/annotation. Explicit enum markers remain validated, and malformed typed markers fail at the producer boundary.
Generated protobuf artifacts are updated for C++, Python, Rust, legacy TypeScript, and TypeScript2. Go generated bindings are intentionally excluded because this is the shared upstream-only PR; proto backward compatibility is not a design constraint for this work.
Validation
Local validation on the final canary-merged branch:
--workspace --all-targets --all-features -- -D warnings)bridge_ctypes,bex_external_types,bex_enginelibrary suitestype_shapesSDKtype_shapesSDKtype_shapesSDKtype_shapesSDKFocused conversion tests cover the exact nested empty-list example above, unannotated empty-container ambiguity, literal-vs-primitive ambiguity, exact annotation/payload mismatch, recursive aliases, nominal generic refinement, ambiguous concrete generic arms, recursive class/generic propagation, conflicting type arguments, outbound index derivation, and malformed/out-of-range selection.
Commit structure
The feature commits remain separated by shared concern:
feat(bridge): preserve canonical selected union armsfeat(codegen): lower class Self before SDK generationfeat(runtime): enforce shared boundary type matchingfeat(runtime): preserve declared host callback argumentsfeat(bridge): generalize inbound typed valuesfix(bridge): address union boundary review findingsrefactor(bridge): use sparse inbound type annotationscanaryand reconcile its C++/WASM host-callable pathsDeliberately out of scope
The result is one shared contract: compiler-lowered types, sparse inbound evidence, recursively applied context, canonical outbound union selection, and the same callback validation semantics for every SDK.
Summary by CodeRabbit
New Features
Selfin class methods now resolves to the owning class, including nested generic types.Bug Fixes