feat(sdk-java): Java/JVM bridge — harness, codegen, runtime, first end-to-end slice - #4060
Conversation
Bridge Week step 1 for the Java/JVM bridge (bridge-java): - sdkgen_java: stub emitter crate mirroring sdkgen_go's shape, with the to_source_code_with_bytecode signature the harness calls. - sdk_test_harness_setup::java + sdk_test_java crate: fixture discovery, codegen under catch_unwind (stub panics downgrade to build_diagnostics records), per-fixture Gradle staging (build.gradle.kts / settings.gradle.kts, Java 17 toolchain + foojay resolver), recursive customizable/ copy into generated/tests/, and the javac/junit nextest targets — all #[ignore]d while sdkgen_java is a stub. setup.sh/.ps1 stage a shared Gradle home under target/gradle-home and write the setup_guard breadcrumb. - Ported all 283 python_pydantic2 parity tests 1:1 (byte-identical test method names) across type_shapes, function_calls, llm_functions, and docstrings_etc into JUnit 5 sources; deviations are marked with java-port notes, 4 tests @disabled with reasons. - Reference docs: ref-java-state-of-completeness.md (capability checklist vs Python) and ref-java-codegen-conventions.md (the generated-API conventions the tests encode: baml_sdk package root, PreserveCase, Fns holders, CompletableFuture async siblings, sealed interface + records for unions, configurator optional args). Note: committed with --no-verify — the pre-commit clippy hook fails on a pre-existing question_mark lint in baml_lsp2_actions under the local nightly (1.98.0); clean under the CI-pinned 1.93.0 toolchain.
…ase 17 Java (temurin-23) was already pinned in mise.toml; add gradle 8.14 so dev environments and CI get it from the same place. With a mise- provided JDK guaranteed, the fixture Gradle template drops the foojay toolchain auto-provisioning and strict 17 toolchain in favor of javac --release 17 — same Java 17 API floor, one JDK, no plugin-portal dependency at settings-evaluation time. Verified: gradle compileJava evaluates the template (NO-SOURCE), and compileTestJava fails only on 'package baml_sdk does not exist' — the intended red state until sdkgen_java lands. (SKIP=cargo-clippy: pre-existing question_mark lint in baml_lsp2_actions under local nightly; clean on CI's 1.93.0.)
Replaces the stub with a real emitter producing a structurally correct baml_sdk/ tree: one .java file per top (Java's one-public- type-per-file rule; no barrel files — packages are directories), per-package Fns free-function holders (Fns$ on collision), a root Baml.java runtime anchor, and the compiled bytecode as a base64 classpath resource (inlinedbaml.b64 — a byte-array literal would blow Java's 64KB static-initializer limit). Routing ports sdkgen_typescript_node's rules verbatim, plus the keyword sanitization Java needs: reserved-word segments $-escape (the void fixture namespace emits as package baml_sdk.void$). Placeholder bodies are phase-2 scope: names, generic params, enum variants, Javadoc — members land with translate_ty. The five runtime-owned stdlib types (media + Stream) are skipped, mirroring the TS re-export set. 22 unit tests. Harness: codegen now runs directly (panics abort the build); build_diagnostics + setup_guard are un-ignored and pass. Verified gradle compileJava succeeds on all four fixtures' generated trees under --release 17; compileTestJava remains red on missing members (the phase-4 gate). (SKIP=cargo-clippy: pre-existing lint in baml_lsp2_actions under local nightly; sdkgen crates verified clean on CI's 1.93.0.)
translate_ty: exhaustive BAML Ty -> Java type-expression function. Fully-qualified names everywhere (no import machinery needed in Java). Position-aware primitive boxing (long vs java.lang.Long — boxing is the nullability story). T|null collapses to the boxed inner type; multi-arm unions mint nominal sealed types named Union<Arm>Or<Arm> (declaration order) and are collected through a UnionSink for phase-4 file emission; literal unions over one base erase to the base. Non-recursive aliases erase via an alias table; recursive aliases keep a nominal name. Callables map to java.util.function shapes by arity. 16 new tests (38 total). ref-java-packaging.md: ground-truthed three-tier shipping story (wrapper/toolchain CLI, per-ecosystem runtime bridge packages, never-published generated code), the Maven plan (baml-bridge main JAR + per-platform native JARs via Gradle Module Metadata variants, channel versioning), generated-code placement options A/B/C with the Gradle-plugin target state, and the stale-output-dir hazard. (SKIP=cargo-clippy: pre-existing baml_lsp2_actions lint on local nightly; sdkgen_java verified clean on CI's 1.93.0.)
output_type = "java" in a baml.toml [generator.<name>] table now dispatches to sdkgen_java::to_source_code_with_bytecode, same shape as the python/typescript arms. (SKIP=cargo-clippy: pre-existing baml_lsp2_actions lint on local nightly.)
…s, unions Classes render with private final fields, a canonical all-args constructor (declaration order), PreserveCase accessors, and deep value equality (== for primitives, Arrays.equals for byte[], Objects.equals otherwise; hashCode consistent). Free functions render as static sync + _async bindings on the per-package Fns holder, calling the baml_bridge.BamlFfi runtime entry points (callSync returning Object cast to the declared type, callAsync thenApply'd to the boxed shape); required args only for now, so omitted optionals hit engine defaults. Minted unions from translate_ty's UnionSink emit as sealed interface + records, drained to a fixpoint (union arms can mint further unions); recursive aliases over unions reuse the union renderer under the alias's name. Regenerated fixtures now fail compileJava ONLY on the not-yet-written baml_bridge Java runtime library (BamlFfi/BamlHandle) — the next critical-path piece. (SKIP=cargo-clippy: pre-existing baml_lsp2_actions lint on local nightly; sdkgen_java clean on 1.93.0.)
bridge_java (Rust cdylib, jni 0.21 via workspace dep): in-process JNI binding linking bridge_cffi — nativeInitFromBytecode / nativeCallSync (blocking, the bridge_python call_function_sync port) / nativeNewCallId, exported for baml_bridge.BamlFfi. sdks/java/baml_bridge (Gradle, --release 17): BamlFfi (System.load via BAML_JAVA_BRIDGE_LIB env or baml.bridge.lib property; callSync encodes CallFunctionArgs, decodes BamlOutboundResult; callAsync = supplyAsync for now), real BamlError/BamlPanic, hand-rolled proto3 wire codec for the primitives slice (zero deps; lenient error/panic value decode mirroring Python's no-typemap fallback), compile-only stubs for BamlStream/BamlHandle/BamlType(s)/BamlCallContext/ BridgeEnv/etc, and runtime-owned baml_sdk.baml.media classes. Verified: cdylib clippy-clean on 1.93.0, jar builds, 18 codec tests + a JNI smoke test that round-trips encode -> engine -> decode. sdkgen_java: class static/instance methods now emit real bindings via a shared render_callable_pair (receiver as required param 0 named self, this as arg 0), 40 emitter tests. Wiring: fixture template links the baml-bridge jar and forwards BAML_JAVA_BRIDGE_LIB into the Test task; setup.sh/.ps1 build the cdylib + jar before the breadcrumb. (SKIP=cargo-clippy: pre-existing baml_lsp2_actions lint on local nightly; bridge crates verified clean on 1.93.0.)
… engine
Runtime init: the generated Baml.java anchor now loads
baml_sdk/inlinedbaml.b64 from the classpath in a static initializer
(Base64 MIME decode -> BamlFfi.initFromBytecode, idempotent); every
Fns holder forces it via Baml.ensure() — the Java analog of Python's
root-package import side effect. The fixture template registers the
resource root.
Emitter fixes surfaced by compiling the full stdlib surface:
- runtime-owned baml.llm.Stream type references resolve to
baml_bridge.BamlStream (no generated class exists)
- method-level generic params are declared; static methods on generic
classes re-declare the class type vars (JLS: no static access)
- unions with type-variable arms are generic over them, records
carrying the full param list
- unit-typed parameters box to java.lang.Void ('void' is return-only)
- thenApply lambda var renamed v$ (collides with BAML params named v)
Test-compile exclusions: tests/compile-excludes.txt per fixture —
Java compiles all test sources together, so sources for
not-yet-implemented capabilities (streams/aliases/forward-refs/
handles/void: 5 files) are excluded, the compile-time analog of
#[ignore]; goal is an empty file.
Result on type_shapes with the real engine (encode -> JNI ->
bex_engine -> decode): TestPrimitives 12/14 (the 2 failures need
class-value codec, a step-5 capability), TestMain 5/5, TestLiterals
6/7, TestLists 3/5, TestMaps 2/4 — 30/90 non-excluded tests green.
(SKIP=cargo-clippy: pre-existing baml_lsp2_actions lint on local
nightly; sdkgen_java clean on 1.93.0.)
Flip verified rows after the end-to-end slice: free functions (sync), required positional args, normal return, and the six primitive value kinds to ✅; async siblings, class methods, optional-args omission, error/panic surfacing, bigint, lists, and maps to 🚧 (implemented but not parity-verified). Status paragraph records the test evidence.
|
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:
📝 WalkthroughWalkthroughJava SDK support is added across code generation, JNI runtime bridging, protobuf encoding/decoding, Gradle-based fixture tests, Maven packaging, release automation, examples, and reference documentation. ChangesJava SDK implementation
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ 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 |
Baml.java's static initializer now registers every generated class (BAML FQN, Java binary name, field declaration order) and enum (FQN, Java constants, wire variant names) with baml_bridge.TypeRegistry before runtime init. Field order rides explicitly because the JVM does not guarantee getDeclaredFields() order and the decoder constructs via the canonical constructor positionally; enum arrays are parallel so keyword-escaped constants (new$) map to their wire spelling (new). (SKIP=cargo-clippy: pre-existing baml_lsp2_actions lint on local nightly; sdkgen_java clean on 1.93.0.)
There was a problem hiding this comment.
Actionable comments posted: 19
🧹 Nitpick comments (4)
baml_language/crates/baml_codegen_types/src/generator_fields.rs (1)
19-21: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUpdate the output type allowlist error message in CLI validation.
The new
"java"output type is added here, but the hardcoded error message inbaml_cli/src/generate.rs(parse_required_property::<OutputType>) still omits it (r#"one of: "python/pydantic", "python/pydantic/v1", "typescript/node"#"). If a user misspells an output type, the CLI will not list"java"as a valid option.Consider updating the error message in
baml_language/crates/baml_cli/src/generate.rsto include"java".🤖 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/crates/baml_codegen_types/src/generator_fields.rs` around lines 19 - 21, Update the hardcoded invalid-output-type message in parse_required_property::<OutputType> within the CLI generation validation to include "java" alongside the existing allowed output types, keeping the error format unchanged.baml_language/sdks/java/baml_bridge/src/test/java/baml_bridge/BamlFfiSmokeTest.java (1)
31-33: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse
System.mapLibraryNamefor cross-platform extension support.Hardcoding
.sowill cause the smoke test to skip on macOS (.dylib) and Windows (.dll), even if the Rust library was built successfully.You can use
System.mapLibraryNameto dynamically generate the correct platform-specific library name.♻️ Proposed refactor
- // Conventional dev location relative to this Gradle project dir. - path = "../../../target/debug/libbridge_java.so"; + // Conventional dev location relative to this Gradle project dir. + String libName = System.mapLibraryName("bridge_java"); + path = "../../../target/debug/" + libName;🤖 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/java/baml_bridge/src/test/java/baml_bridge/BamlFfiSmokeTest.java` around lines 31 - 33, Update the native library path construction in BamlFfiSmokeTest to use System.mapLibraryName for the bridge library name instead of hardcoding the .so extension. Preserve the existing relative target/debug location while allowing the smoke test to resolve the platform-specific .dylib or .dll filename.baml_language/sdks/java/baml_bridge/src/test/java/baml_bridge/WireCodecTest.java (1)
194-203: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert the encoded kwargs and call ID structure.
This test passes for any nonempty byte sequence, so it cannot catch wrong field numbers or values. Decode the top-level fields with
WireReaderand assert field 1 contains key"n"and integer7, while field 2 equals123.🤖 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/java/baml_bridge/src/test/java/baml_bridge/WireCodecTest.java` around lines 194 - 203, Update inbound_encodes_call_id_and_kwargs to decode the encoded bytes with WireReader instead of only checking bytes.length. Assert that top-level field 1 contains the kwarg key "n" with integer value 7, and field 2 contains call ID 123, preserving the test’s validation of the expected protobuf structure.baml_language/sdk_tests/crates/java/function_calls/customizable/TestRaises.java (1)
110-113: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse
assertFalseinstead ofassertTruewith a negation.♻️ Proposed refactor
`@Test` void test_non_throwing_function_has_no_raises_block() throws IOException { String block = javadocFor(source("Fns"), "PureLen("); - assertTrue(!block.contains("`@throws`"), block); + assertFalse(block.contains("`@throws`"), block); }🤖 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/sdk_tests/crates/java/function_calls/customizable/TestRaises.java` around lines 110 - 113, Update test_non_throwing_function_has_no_raises_block to replace the negated assertTrue assertion with assertFalse, preserving the same block.contains("`@throws`") condition and diagnostic message.
🤖 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/sdk_tests/crates/java/function_calls/customizable/TestCancellation.java`:
- Around line 42-44: Disable the TestCancellation class until cancellation and
async execution are implemented by adding the JUnit `@Disabled` annotation with a
clear capability-not-implemented reason; retain the existing test methods
unchanged.
In
`@baml_language/sdk_tests/crates/java/function_calls/customizable/TestHostCallables.java`:
- Around line 187-208: Update
test_multiple_throws_in_flight_do_not_collide_in_registry to execute both
Fns.call_with_callback invocations concurrently using CompletableFuture tasks
and a synchronization barrier inside the callbacks, ensuring both exceptions are
in flight before release. Await both failures, then assert each raised exception
identity and that the results are distinct.
In
`@baml_language/sdk_tests/crates/java/llm_functions/customizable/TestMain.java`:
- Around line 92-100: Update test_root_imports_cleanly to force initialization
of baml_sdk.Fns by calling Class.forName with initialization enabled, using
baml_sdk.Fns.class.getClassLoader() and the existing fully qualified class name.
Replace the class-literal-only assertion so the test exercises the static
runtime bootstrap.
In
`@baml_language/sdk_tests/crates/java/llm_functions/customizable/TestStreamingE2e.java`:
- Around line 71-193: Add a JUnit `@Timeout` annotation at the class level or to
each affected streaming test method, covering synchronous next(), asynchronous
join(), and collection calls in test_stream, test_stream_async,
test_stream_collect_in_baml, test_stream_doc, test_stream_doc_async, and
test_stream_doc_collect_in_baml. Keep the existing result-count guards
unchanged.
In
`@baml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestHandles.java`:
- Around line 107-132: Update test_file_cursor_state_persists_across_calls so
the File handle is declared outside the try block and closed from finally,
ensuring cleanup runs when any assertion or file operation fails. Preserve the
existing assertions and avoid relying on the success-path f.close() call.
In `@baml_language/sdks/agent-docs/bridge-ref/ref-java-codegen-conventions.md`:
- Around line 28-29: The Java SDK documentation has conflicting async method
naming conventions. In
baml_language/sdks/agent-docs/bridge-ref/ref-java-codegen-conventions.md lines
28-29, establish the canonical async suffix, and in
baml_language/sdks/agent-docs/bridge-ref/ref-java-state-of-completeness.md lines
19-20, remove the conflicting alternative and align the status table with that
same convention.
- Around line 57-60: Update the Runtime init convention to specify an active
initialization trigger rather than class-literal loading. Document the exact
entry point, such as Baml.ensure() or Class.forName(..., true, ...), and ensure
the smoke test invokes it so the root holder’s static initializer executes.
In `@baml_language/sdks/agent-docs/bridge-ref/ref-java-state-of-completeness.md`:
- Line 13: The aggregate test totals in the status entry are inconsistent with
the explicitly listed suite results. Update the summary to match the listed
28/35 total, or document the additional suites and their results that account
for 30/90; keep the individual suite counts accurate.
In
`@baml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/BamlCallContext.java`:
- Around line 8-15: Make the BamlCallContext constructor public so downstream
callers can instantiate it with new BamlCallContext(). Leave the abort()
behavior unchanged.
In `@baml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/BamlFfi.java`:
- Around line 33-44: Replace the unbounded ASYNC_POOL created by
Executors.newCachedThreadPool in BamlFfi with a bounded ThreadPoolExecutor that
limits concurrent blocking callAsync tasks and uses an explicit queue/rejection
policy. Preserve daemon thread creation and the existing callAsync behavior,
while ensuring bursts cannot create unlimited JVM threads.
- Around line 54-71: Update resolveLibraryPath() in BamlFfi so the configured
system-property or environment-variable value is converted to an absolute,
normalized path before being returned to System.load. Preserve the existing
missing-configuration validation and error message behavior.
In
`@baml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/internal/ProtoReader.java`:
- Around line 195-244: Update decodeValue to track whether any supported value
arm was decoded, including values assigned through recognized cases. After
processing the fields, when lenient is false and no supported arm was found,
throw the appropriate unsupported-value error instead of returning null for
unknown oneof fields; preserve lenient behavior and existing decoding for
supported arms.
- Around line 128-134: Reject envelopes with no decoded oneof arm in
ProtoReader’s result-decoding switch by throwing the appropriate codec exception
instead of returning null. Update WireCodecTest’s corresponding assertion to
expect that exception for an all-default or empty response.
In `@baml_language/sdks/java/sdkgen_java/src/emit.rs`:
- Around line 312-317: Update the Java method-name resolution around
java_identifier and the generated async-method emission so callable names are
disambiguated as a group. Detect when a user function such as foo_async occupies
the same signature as foo’s generated async sibling, then escape or rename the
generated sibling before emitting methods. Apply the same resolution
consistently at both name-generation sites, while preserving distinct signatures
and existing names when no collision exists.
- Around line 120-181: Update the class emission logic around
render_callable_pair so generated classes containing static or instance method
bindings include a static initialization block that invokes
baml_sdk.Baml.ensure(). Emit it before any generated constructors or methods can
be called, matching the initialization behavior used by Fns holders, while
leaving classes without bindings unchanged.
- Around line 54-60: Update the Javadoc line emission loop in the doc generation
function to escape backslashes before emitting each line, preventing
Unicode-escaped comment terminators from closing the block. Preserve the
existing replacement of literal `*/`, and ensure both transformations are
applied to the generated output.
- Around line 67-82: Update the FieldEq classification and equality/hashCode
emission around field_eq so double fields use a dedicated comparison strategy
rather than Primitive. Generate Double.compare(this.x, other.x) == 0 for double
equality and Double.hashCode(this.x) for hashing, while preserving existing
primitive handling for long and boolean.
In `@baml_language/sdks/java/sdkgen_java/src/lib.rs`:
- Around line 167-169: Update the Java generation flow around the type_files
insertion and runtime anchor creation so a root user type named Baml cannot be
overwritten. Reserve or escape the generated anchor filename, or choose a
collision-free anchor identifier and propagate it through every ensure()
reference. Add a regression test covering a root Baml class or enum and verify
both generated outputs remain available.
In `@baml_language/sdks/java/sdkgen_java/src/translate_ty.rs`:
- Around line 192-200: The union identity generated around union_ident and
union_arm_token must distinguish complete arm types, including qualified class
names and generic arguments. Update the identifier or key construction used
before sink.unions.insert so unions such as different-package classes and
differently parameterized wrappers cannot collide; preserve identical unions as
duplicates or explicitly reject conflicting duplicate keys rather than silently
overwriting them.
---
Nitpick comments:
In `@baml_language/crates/baml_codegen_types/src/generator_fields.rs`:
- Around line 19-21: Update the hardcoded invalid-output-type message in
parse_required_property::<OutputType> within the CLI generation validation to
include "java" alongside the existing allowed output types, keeping the error
format unchanged.
In
`@baml_language/sdk_tests/crates/java/function_calls/customizable/TestRaises.java`:
- Around line 110-113: Update test_non_throwing_function_has_no_raises_block to
replace the negated assertTrue assertion with assertFalse, preserving the same
block.contains("`@throws`") condition and diagnostic message.
In
`@baml_language/sdks/java/baml_bridge/src/test/java/baml_bridge/BamlFfiSmokeTest.java`:
- Around line 31-33: Update the native library path construction in
BamlFfiSmokeTest to use System.mapLibraryName for the bridge library name
instead of hardcoding the .so extension. Preserve the existing relative
target/debug location while allowing the smoke test to resolve the
platform-specific .dylib or .dll filename.
In
`@baml_language/sdks/java/baml_bridge/src/test/java/baml_bridge/WireCodecTest.java`:
- Around line 194-203: Update inbound_encodes_call_id_and_kwargs to decode the
encoded bytes with WireReader instead of only checking bytes.length. Assert that
top-level field 1 contains the kwarg key "n" with integer value 7, and field 2
contains call ID 123, preserving the test’s validation of the expected protobuf
structure.
🪄 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: ca950fe8-2981-4ca8-8e6a-186438108ca8
⛔ Files ignored due to path filters (1)
baml_language/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (91)
baml_language/.config/nextest.tomlbaml_language/.markdown-whitelistbaml_language/Cargo.tomlbaml_language/crates/baml_cli/Cargo.tomlbaml_language/crates/baml_cli/src/generate.rsbaml_language/crates/baml_codegen_types/src/generator_fields.rsbaml_language/sdk_tests/crates/java/Cargo.tomlbaml_language/sdk_tests/crates/java/build.rsbaml_language/sdk_tests/crates/java/docstrings_etc/customizable/TestMain.javabaml_language/sdk_tests/crates/java/function_calls/customizable/OptionalArgsStatic.javabaml_language/sdk_tests/crates/java/function_calls/customizable/TestCancellation.javabaml_language/sdk_tests/crates/java/function_calls/customizable/TestErrors.javabaml_language/sdk_tests/crates/java/function_calls/customizable/TestGenericCalls.javabaml_language/sdk_tests/crates/java/function_calls/customizable/TestGenericInference.javabaml_language/sdk_tests/crates/java/function_calls/customizable/TestHostCallables.javabaml_language/sdk_tests/crates/java/function_calls/customizable/TestMain.javabaml_language/sdk_tests/crates/java/function_calls/customizable/TestMethodsOnClasses.javabaml_language/sdk_tests/crates/java/function_calls/customizable/TestOptionalArgs.javabaml_language/sdk_tests/crates/java/function_calls/customizable/TestRaises.javabaml_language/sdk_tests/crates/java/function_calls/customizable/TestStdlibEntrypoints.javabaml_language/sdk_tests/crates/java/llm_functions/customizable/ReplayHarness.javabaml_language/sdk_tests/crates/java/llm_functions/customizable/TestMain.javabaml_language/sdk_tests/crates/java/llm_functions/customizable/TestStreamingE2e.javabaml_language/sdk_tests/crates/java/setup.ps1baml_language/sdk_tests/crates/java/setup.shbaml_language/sdk_tests/crates/java/src/lib.rsbaml_language/sdk_tests/crates/java/type_shapes/customizable/TestComplexModels.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/TestGeneric.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/TestMain.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/compile-excludes.txtbaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestAliases.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestClassRefs.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestEnums.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestForwardRefs.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestGenerics.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestHandles.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestLists.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestLiterals.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestMaps.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestMedia.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestOptional.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestPrimitives.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestRecursion.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestRouting.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestStreams.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestSymbolCollisions.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestUnions.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestVoid.javabaml_language/sdk_tests/harness_runner/src/lib.rsbaml_language/sdk_tests/harness_setup/Cargo.tomlbaml_language/sdk_tests/harness_setup/src/java.rsbaml_language/sdk_tests/harness_setup/src/lib.rsbaml_language/sdk_tests/harness_setup/src/templates/build.gradle.ktsbaml_language/sdk_tests/harness_setup/src/templates/settings.gradle.ktsbaml_language/sdks/agent-docs/bridge-ref/ref-java-codegen-conventions.mdbaml_language/sdks/agent-docs/bridge-ref/ref-java-packaging.mdbaml_language/sdks/agent-docs/bridge-ref/ref-java-state-of-completeness.mdbaml_language/sdks/java/baml_bridge/.gitignorebaml_language/sdks/java/baml_bridge/build.gradle.ktsbaml_language/sdks/java/baml_bridge/settings.gradle.ktsbaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/BamlCallContext.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/BamlCancelledError.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/BamlError.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/BamlFfi.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/BamlHandle.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/BamlOptions.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/BamlPanic.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/BamlStream.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/BamlType.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/BamlTypes.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/BridgeEnv.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/StreamFinished.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/internal/ProtoReader.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/internal/ProtoWriter.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/internal/WireReader.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/internal/WireWriter.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_sdk/baml/media/Audio.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_sdk/baml/media/Image.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_sdk/baml/media/Pdf.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_sdk/baml/media/Video.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_sdk/baml/stream/StreamFinished.javabaml_language/sdks/java/baml_bridge/src/test/java/baml_bridge/BamlFfiSmokeTest.javabaml_language/sdks/java/baml_bridge/src/test/java/baml_bridge/WireCodecTest.javabaml_language/sdks/java/bridge_java/Cargo.tomlbaml_language/sdks/java/bridge_java/src/lib.rsbaml_language/sdks/java/sdkgen_java/Cargo.tomlbaml_language/sdks/java/sdkgen_java/src/emit.rsbaml_language/sdks/java/sdkgen_java/src/lib.rsbaml_language/sdks/java/sdkgen_java/src/routing.rsbaml_language/sdks/java/sdkgen_java/src/translate_ty.rsmise.toml
TypeRegistry: FQN -> (lazy Class.forName, field declaration order, enum wire-name maps), reverse index by binary class name for encode; ConcurrentHashMap, idempotent. ProtoReader reifies class_value via the canonical constructor (registry field order; unknown FQN falls back to the lenient field map) and enum_value via the wire-name map. ProtoWriter encodes registered classes (FQN on class_ty.name, fields read through the public accessors) and enums. 8 new codec tests (27 total green). type_shapes against the live engine: 71/90 (was 30/90) — TestPrimitives 14/14, TestEnums 5/5, TestClassRefs 3/3, TestGenerics 6/6, TestRouting 9/9, TestRecursion 4/4, TestSymbolCollisions 5/5, TestMaps 4/4, TestLiterals 7/7. Remaining red = unions (decode next), media, and union-adjacent stragglers. (SKIP=cargo-clippy: pre-existing baml_lsp2_actions lint on local nightly.)
Every minted union (incl. recursive-alias unions) registers with TypeRegistry: a sorted-arm-token signature (order-insensitive so engine-side normalization can't mismatch; null arms excluded), the sealed interface's binary name, and per-arm (token, record binary name) pairs in declaration order. Tokens are the cross-side contract (primitive keywords, canonical FQNs incl. the user. package for class/enum arms, lit:<base>:<value> for literals, list<>/map<,> composites) — the Java runtime derives identical tokens from the wire self_type BamlTy. (SKIP=cargo-clippy: pre-existing baml_lsp2_actions lint on local nightly; sdkgen_java clean on 1.93.0.)
Runtime: BamlTy tokenizer over the wire self_type; sealed-union registry keyed by the sorted-distinct arm-token signature; record arm picked from the inner value's shape (value_option_name is unreliable for literal arms); unknown signature falls back to the bare inner value (load-bearing: erased literal unions decode as their base). Inbound unwraps registered union records to the bare inner value. Two integration lessons baked in: - Minted unions now live in ONE canonical package (baml_sdk.unions$) instead of per-package twins — same structural union must be the same Java type everywhere or encode/decode identity breaks; $ keeps the package collision-free from BAML namespaces. - The signature is duplicate-insensitive on both sides: the engine's runtime union_type keeps duplicate arms (int|int|string) that the TIR-side registration never sees. Caught live by round_trip_dedup. type_shapes against the engine: 80/90 — TestUnions 6/6, TestOptional 5/5, remaining reds are media (0/9, later capability) and one generic-union known gap. (SKIP=cargo-clippy: pre-existing baml_lsp2_actions lint on local nightly.)
compile-excludes.txt shrinks to streams + handles. TestVoid adapts to Java (void call -> assertDoesNotThrow); alias/forward-ref tests adopt the deterministic list-arm record names (RecListListValue). Java BamlTy tokenizer maps type_alias nodes to their canonical FQN and recursive-alias unions additionally register under the alias's own FQN signature. Known upstream gap (kept honestly red, 4 tests): bex_engine's maybe_wrap_union does not dereference recursive aliases (RuntimeTy:: TypeAlias falls through unwrapped), so recursive-alias unions cross the wire bare. Python decode never noticed; every statically-typed bridge needs the wrapper. Needs an engine-side fix — raising with the team. type_shapes: 84/98. Red: 4 recursive-alias round-trips (engine gap), 1 generic-union (known), 9 media (later capability). (SKIP=cargo-clippy: pre-existing baml_lsp2_actions lint.)
…decode — 89/98
TEAM DECISION: anonymous unions render as the runtime's sealed
generic arity family baml_bridge.Union{2..10}<...> (nested generic
records Arm0..Arm{n-1} in BAML declaration order — exhaustive switch
on Java 21+, instanceof on 17; >10 arms -> Object until the
threshold/alias policy). Arm selection is type-directed (Kai's
model): generated bindings pass a descriptor string (grammar in
ref-java-codegen-conventions.md) as the last callSync/callAsync arg;
registerClass carries per-field descriptors; the decoder matches the
wire value against declared arms in source order. The wire/proto
layer is untouched — descriptors are host-side; error/panic decode
stays wire-driven, as does anything without a descriptor.
Deleted: nominal union minting for anonymous unions (the naming
problem dissolves). Kept: recursive aliases as nominal sealed types
(positional generics cannot self-reference) — their arms now decode
type-directed via the matched arm token, which fixes the recursive-
alias round-trips WITHOUT the engine maybe_wrap_union fix (bare wire
values reify from the call site's declared type). Encoder unwraps
the family via a BamlUnion marker interface.
type_shapes against the live engine: 89/98 — unions 6/6, optional
5/5, lists 5/5, complex models 1/1, generic-union 2/2, aliases 3/3,
forward-refs 4/4. Only media (0/9, later capability) remains red.
(SKIP=cargo-clippy: pre-existing baml_lsp2_actions lint on local
nightly; sdkgen_java 45 tests + clippy clean on 1.93.0.)
Union row records the team decision (generic arity family + type-directed decode); enums/classes/containers/implicit generics flip to verified; status paragraph carries the 89/98 evidence with media as the only red capability.
…orkflow draft harness_runner gains run_java_test_cmd (injects BAML_JAVA_BRIDGE_LIB pointing at the workspace cdylib); the emitted junit targets use it. type_shapes::javac is un-ignored via a GREEN_JAVAC_FIXTURES list — verified live under nextest (setup.sh fires, gate passes) — so CI now enforces compilation of the full generated tree + test sources. junit stays ignored until media flips the last 9 reds. build2-java-sdk.reusable.yaml: DRAFT per-platform native-jar matrix (tier-1 trio) + main-jar job, modeled on the nodejs reusable workflow; needs release-owner review before wiring into release-baml-language. (SKIP=cargo-clippy: pre-existing baml_lsp2_actions lint.)
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 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/sdks/java/baml_bridge/src/main/java/baml_bridge/internal/ProtoReader.java`:
- Around line 979-983: Update the union-arm selection loop in ProtoReader around
armMatchesToken and decodeWithDesc so container arms are validated against their
contents recursively, not accepted solely because their outer wire kind matches.
Ensure ambiguous matches such as union[list<int>;list<string>] are rejected or
otherwise disambiguated, rather than always selecting the first matching arm and
returning an incorrect wrapArm result; apply the same fix to the corresponding
logic near the additional referenced block.
- Around line 1231-1249: Update the literal descriptor encoding and the lit:
parsing branch in ProtoReader to use a symmetric escape or length-prefix scheme.
Ensure emitters encode literal values containing ;, ], >, or , and the parser
decodes them without treating escaped characters as structural terminators.
Preserve spaces and literal content exactly so valid descriptors parse normally
without wire-driven fallback.
- Around line 1012-1018: Update the LIT handling in ProtoReader so literal wire
tokens match only the exact encoded literal value, removing the same-base
startsWith fallback that causes one literal arm to capture another. Preserve the
existing bare base-type match for non-literal scalar tokens.
- Around line 1005-1007: Update the FQN branch in ProtoReader’s arm-matching
logic to delegate registered recursive-alias resolution to TypeRegistry instead
of comparing only arm.text with the token. Preserve the existing direct text
comparison for PRIM arms, and ensure aliases use their structural wire
discriminator when matching union arms.
In
`@baml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/TypeRegistry.java`:
- Around line 143-149: Update the union registration logic around UnionEntry and
unionsBySignature to reject conflicting registrations when the same structural
signature has a different sealedInterfaceName, rather than silently retaining
the first entry. Ensure conflicting aliases cannot register their record names
for encoding, while preserving existing behavior for identical nominal
registrations.
In `@baml_language/sdks/java/sdkgen_java/src/lib.rs`:
- Around line 378-382: Update signature_token and the related record-name
generation to preserve each union arm’s complete qualified structural identity.
Include class generic arguments in class tokens instead of reducing them to
fqn(name), and retain module/package qualification for named arms so distinct
types such as a.Node and b.Node cannot collide. Apply the same identity
consistently at the referenced record-name logic around the additional location.
🪄 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: cd84d0fd-2250-4b23-9b7e-3a3a8e7e1b4f
📒 Files selected for processing (30)
baml_language/sdk_tests/crates/java/type_shapes/customizable/TestComplexModels.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/TestGeneric.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/compile-excludes.txtbaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestAliases.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestForwardRefs.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestLists.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestOptional.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestStreams.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestUnions.javabaml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestVoid.javabaml_language/sdks/agent-docs/bridge-ref/ref-java-codegen-conventions.mdbaml_language/sdks/agent-docs/bridge-ref/ref-java-state-of-completeness.mdbaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/BamlFfi.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/BamlUnion.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/TypeRegistry.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/Union10.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/Union2.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/Union3.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/Union4.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/Union5.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/Union6.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/Union7.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/Union8.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/Union9.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/internal/ProtoReader.javabaml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/internal/ProtoWriter.javabaml_language/sdks/java/baml_bridge/src/test/java/baml_bridge/WireCodecTest.javabaml_language/sdks/java/sdkgen_java/src/emit.rsbaml_language/sdks/java/sdkgen_java/src/lib.rsbaml_language/sdks/java/sdkgen_java/src/translate_ty.rs
💤 Files with no reviewable changes (1)
- baml_language/sdk_tests/crates/java/type_shapes/customizable/compile-excludes.txt
🚧 Files skipped from review as they are similar to previous changes (10)
- baml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestAliases.java
- baml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestUnions.java
- baml_language/sdks/agent-docs/bridge-ref/ref-java-codegen-conventions.md
- baml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestLists.java
- baml_language/sdk_tests/crates/java/type_shapes/customizable/TestComplexModels.java
- baml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestForwardRefs.java
- baml_language/sdk_tests/crates/java/type_shapes/customizable/roundtrip_tests/TestOptional.java
- baml_language/sdks/java/baml_bridge/src/main/java/baml_bridge/BamlFfi.java
- baml_language/sdks/agent-docs/bridge-ref/ref-java-state-of-completeness.md
- baml_language/sdks/java/sdkgen_java/src/emit.rs
…ublishing + quickstart
BamlFfi loads the engine via a first-hit-wins ladder: baml.bridge.lib
property -> BAML_JAVA_BRIDGE_LIB env (dev/test) -> classpath resource
/native/{os}-{arch}/{lib} extracted from the natives classifier jar
(NativeLibraryLoader, unit-tested incl. the darwin-contains-'win'
trap). maven-publish: com.boundaryml.baml:baml-bridge, POM + nativeJar
task parameterized per platform, version via -PbamlVersion; harness
consumption (plain baml-bridge.jar name) preserved; PUBLISHING.md
documents the CI contract (credentials, GPG, per-platform props).
examples/quickstart: real consumer project — baml.toml + baml_src,
'baml generate --output_type java' (325 files), Gradle resolving the
artifact from mavenLocal and running against the EMBEDDED native lib
with zero env vars. Verified live: add(2,3)=5 through the engine.
(SKIP=cargo-clippy: pre-existing baml_lsp2_actions lint.)
The owner-decided D3 surface, completing the biggest function_calls slice (+90 tests): - Generic callables gain trailing baml_bridge.BamlTypes overloads in the fixed order f(required.., opts?, types?, ctx?) — worst case 16 methods for a generic+optional function; synthetic param names yield to user arguments via the trailing-$ escape. - Generic classes gain reified static of(BamlType.., fields..) factories (bind the side-table on construct) and bamlTypeArgs() readback (escapes iff a BAML field claims the name). Instance-method explicit bags require a reified receiver, mirroring Python. - BamlType grammar is now FULL (owner: 'do B now'): list/map/optional/ union/literal tokens with exact wire round-trips; the decode side-table binds whenever every wire arg is representable. - Required for the engine's Gate-B check: encodeClass now emits class_ty.type_args from the side-table for reified instances, mirroring Python. Union-with-TypeVar lowers to Object (matches the Python twin's behavior; the flagged-TBD type_shapes assertion updated accordingly). - apply() tests (host-callable args) @disabled pending the host-callables slice; TestHostCallables stays the sole exclude. Gates: sdkgen_java 62/62 + clippy clean, baml_bridge 128/0, function_calls 133 total / 0 fail / 5 skips, type_shapes 108/0/0, nextest 6/6. Docs: generic rows flipped, D3 items [decided], BridgeLanguage 7 correction. (SKIP=cargo-clippy: pre-existing baml_lsp2_actions lint.)
The canary-merge renumbering (Cpp took 6, Java moved to 7) updated the Rust enum but missed the published baml_cffi.h and its Go-embedded copy; the completeness doc also still said 6. All three now agree with bridge_cffi/src/ffi/runtime.rs. Header drift tests green. Flagged by CodeRabbit (3605641336) — first of the review-sweep fixes; the rest land after the host-callables slice to avoid file contention. (SKIP=cargo-clippy: pre-existing baml_lsp2_actions lint.)
The last function_calls capability, per the owner-accepted brief (A1-F1): Java-side registry (one ConcurrentHashMap keyspace for callables + opaque throwables; Rust is a pure router), dedicated daemon dispatch executor (user code off the engine threads; the C callback returns promptly per the api.rs contract), value-level async detection (CompletableFuture results awaited), exception identity via registry-key rehydration (assertSame round-trips through sneakyThrow; foreign keys fall through to the metadata BamlError), generated @FunctionalInterface + always-non-null Opts bag for optional-arg callables (IntOptCallback shape, BamlHostCallable marker), and the 1-arg BamlError(value) ctor with typed unwrap so BAML's typed catch matches. C-ABI shapes per source: dispatch fn(u64,u32,*u8,len), release fn(u64), complete_host_call(u32,i32,*i8,len); BamlToHostCall flat declared-order args w/ arg_name + is_optional_arg; HOST_VALUE_CALLABLE =15/OPAQUE=16. Forced divergence recorded: the engine requires the HostCallable traceback field present, so Java always synthesizes one (Python's is conditionally-always via __traceback__). TestHostCallables un-excluded (20 pass + release-on-drop @disabled = Python xfail parity); the 2 apply() generics tests re-enabled and green; compile-excludes is EMPTY. function_calls 154/150/0/4, type_shapes 108/0/0, baml_bridge 128/0, sdkgen_java 64/64, nextest 6/6, clippy clean. Completeness rows flipped (async host-callable row stays in-progress: implemented, no parity test exercises it yet). (SKIP=cargo-clippy: pre-existing baml_lsp2_actions lint.)
…ests, CI
All triaged-valid review items, quick + substantive:
Runtime/codec: double fields compare via Double.compare/Double.hashCode
(NaN/±0.0 equals-contract fix); recursive-alias union arms match via
the registry token path; malformed-bigint messages are length-bounded;
literal descriptor tokens percent-escape the 6 structural chars
symmetrically (Rust emit + Java parse, byte-identical for clean
literals); registerUnion detects same-signature/different-interface
conflicts instead of silent first-wins; generic-args now carried in
signature identity with a safe bare-inner runtime fallback (scoped:
full wire-match symmetry deferred, no fixture exercises it).
Emitter: method-bearing generated classes emit static {
Baml.ensure(); } so any entrypoint boots the runtime; a user function
named foo_async escapes the synthetic sibling to foo_async$; a user
root type named Baml escapes the anchor to Baml$ (threaded through
every ensure() reference); javadoc neutralizes \uXXXX escape runs
(JLS 3.3 backslash-parity) before the */ guard.
Tests/CI: llm_functions TestMain uses Class.forName(init=true);
TestStreamingE2e gains a class @timeout; TestHandles closes the File
in finally; new concurrent host-throw isolation test (2 callables x 32
rounds, identity per-object); workflow interpolations moved to
step-level env (zizmor hygiene) with shell: bash on the matrix step.
Docs: .class-doesn't-init rewording, async-suffix canonicalized,
status line reconciled to final counts.
Gates: sdkgen_java 75/75 + clippy clean, baml_bridge 131 (WireCodec
84/84), type_shapes 108/0/0, function_calls 155/151/0/4, nextest 6/6.
(SKIP=cargo-clippy: pre-existing baml_lsp2_actions lint.)
… fix The canary bytes-at-write-boundary refactor (merged at d7e9f6c) changed generate.rs to Vec<(PathBuf, Vec<u8>)>; our Java arm still returned HashMap<PathBuf, String> — E0308 that cascaded to five CI checks (pre-commit, msrv, windows, macos, snapshot tests). Linux passed spuriously: it alone restores rust-cache and reused a stale baml_cli artifact. The Java arm now converts with into_bytes() like the sibling arms. Also de-links a private const in the harness module docs that tripped -D rustdoc::private-intra-doc-links. Verified on the CI toolchain (1.93.0): cargo check -p baml_cli, cargo doc -D warnings, cargo test --no-run --all-features, clippy over all changed crates — all clean. Remaining red after this push is inherited canary breakage only: Size Gate bridge_wasm +130.5KB fails identically on canary's own tip (baseline drift; canary's refresh job owns it) and the CI-v2 alert is its aggregator. Not absorbed here by design. (SKIP=cargo-clippy: local-nightly artifact; 1.93.0 clippy clean.)
…ties landed
STREAMING (the last unimplemented capability, owner decisions:
get_final() + stdlib-namespace sentinel):
- BamlStream<TPartial,TFinal> is real: wraps the tagged-heap handle;
next()/get_final() (+_async) re-enter the engine on
baml.llm.Stream.next/.final with {self} as receiver, null descriptor
(wire-driven decode, matching Python). Encode delegates to the
BamlHandle arm (cloned key per the drain contract); decode gains the
ADT_TAGGED_HEAP_HANDLE arm -> BamlStream.fromHandle.
- StreamFinished consolidated: runtime-owned at
baml_sdk.baml.stream.StreamFinished, registered in the typemap,
added to RUNTIME_OWNED_FQNS; the baml_bridge stub copy deleted.
Exhaustion returns the StreamFinished VALUE — Python's contract.
- The replay harness was blocked only on BridgeEnv being a stub: built
the native env hook end-to-end (nativeEnvSet/Unset via std::env —
the same process environ the in-process engine reads).
- llm_functions is now fully deterministic offline and GATED: 21/21
(TestStreamingE2e 6/6 on keyless replay; the 3 $build_request
api-key tests converted from junit-pioneer to BridgeEnv — their
documented intended fix — and the root-import test targets the real
anchor). stream_types.* retargets applied per the GAP-B ruling.
ASYNC HOST-CALLABLE: two parity tests for the C1 whenComplete path
(future-returning callable via the documented unchecked idiom, success
+ exceptional completion w/ identity rehydration); no runtime bug
found. Row flipped. $build_request row flipped (api-key tests now
exercise it live).
Gates: sdkgen_java 75/75 + clippy clean; baml_bridge 131; type_shapes
108/0/0; function_calls 157/153/0/4; llm_functions 21/21; nextest 8/8
(javac+junit for all three fixtures, per-fixture test-groups).
(SKIP=cargo-clippy: local-nightly artifact; 1.93.0 clippy clean.)
…dead
Owner-ordered: the stringly-typed descriptor grammar ("union[int;string]",
"list<int>", "lit:string:draft", "tv:T") and its hand-rolled parser are
gone. Generated bindings now pass pooled private static final BamlType
constants (builder expressions rendered by the emitter, deduped per
holder; null = wire-driven, the streaming mode). BamlType gains
decode-only hints (UNKNOWN, typeVar — toWireTy throws) and
classByFqn (no registry lookup, spells runtime-owned FQNs).
No string derivation anywhere: union registry keys are sorted+distinct
List<BamlType> under a structural Comparable (kind → primitiveKind →
fqn → literal → children; consistent with equals) — never a rendered
string, so a crafted literal value cannot alias two arm sets (the
collision class the old grammar needed percent-escaping for).
Recursive-alias entries key by FQN. Wire matching is structural:
self_type reads directly into BamlType; armMatchesValue +
matchesStructural preserve the wave-1 container lattice exactly
(absent/imprecise wire element = wildcard; typed-vs-differently-typed
rejects; empty typed lists land on the type-faithful arm).
Deleted: ProtoReader net -397 lines (Desc tree, parseDesc and the
whole parser family, escapeLiteralValue, the string tokenizers and
lattice); Rust descriptor_token + escape_literal_value.
registerClass/registerUnion take BamlType[]. Docs rewritten (conventions
type-directed section, outbound-decoding, type-mappings column).
Gates: sdkgen_java 75/75 + clippy clean, baml_bridge green,
type_shapes 108/108, function_calls 157/153/0/4, llm_functions 21/21,
nextest 8/8 — all behavior tests unmodified; only
representation-asserting tests ported.
(SKIP=cargo-clippy: local-nightly artifact; 1.93.0 clippy clean.)
…ge, docs audit The last two 🚧 items, plus the mirror-doc staleness sweep: - docstrings_etc ENABLED and green (6/6): the fixture exposed a real emitter gap — Python rolls member docs into one class-level docstring (Attributes:/Members: sections, any-doc visibility rule, no per-member blocks); sdkgen_java now implements the same rollup (ported from format_class_docstring; 7 unit tests). Fourth fixture joins both GREEN gate lists + its nextest test-group. - bigint parity: NEITHER bridge had live coverage (the fixture functions existed unexercised). Added Python-first (2^80, -2^80, the 2^64 hex boundary) + the 1:1 Java port; row flipped with citation. Engine findings recorded: magnitude-directed encode is transparent end-to-end (engine re-emits per declared return type); decode cap is a DoS guard only. type_shapes 110/110. - Mirror docs (examples/outbound/inbound/type-mappings) audited against HEAD and re-quoted from real generated output: typed-descriptor call sites ($RET constants), host callables, streaming, final classes, Double.compare, ensure-blocks, argument-IAE — all stale NOT-YET markers flipped with short-sha history notes; genuinely-open items kept (arity>10 policy, inline media_value, ty_value no-op, the encode-rollback edge). Stale GAP-B OPEN flags corrected to the decided Option B. Every completeness row is now ✅ or explicitly justified; all four fixtures CI-gated javac+junit. Gates: sdkgen_java 82/82 + clippy clean, type_shapes 110/110, function_calls 157/153/0/4, llm_functions 21/21, docstrings_etc 6/6, nextest 10/10, markdown validation green. (SKIP=cargo-clippy: local-nightly artifact; 1.93.0 clippy clean.)
…hing, win/mac CI Java joins the family release machinery, mirroring Python/cffi: Build+publish: release-bridge-java profile (panic=unwind — an engine panic now unwinds into catch_unwind/BamlPanic instead of SIGABRT'ing the consumer JVM; verified building the host cdylib); platforms.json gains the java artifact key on all 8 targets (+2 contract tests, musl classifier suffix); build2-java-sdk rewritten to the family contract (release_plan_json + source_sha, jq-generated matrix, native-arch runners, setup-musl-cross, experimental legs continue-on-error); publish-maven job in the orchestrator: idempotency HEAD against repo1, one signed staging layout (main+sources+javadoc+8 native classifier jars via -PbamlNativeJarsDir), in-memory GPG signing gated on env (useGpgCmd kept for local), bundle zip POSTed to the Central portal with publishingType=AUTOMATIC and status-polled — zero human clicks. Rehearsed end-to-end with an ephemeral key: signatures verify, bundle layout clean. Secrets consumed: CENTRAL_USERNAME/CENTRAL_PASSWORD + GPG_PRIVATE_KEY/GPG_PASSPHRASE. Cross-platform tests: sdk-test-matrix gains java legs on linux/macos/ windows (install-args include java+gradle — test-time spawns resolve gradle from PATH); setup.ps1 hardened ($LASTEXITCODE guards, the descending-range array-slice footgun replaced with splatting); harness gains the gradle->gradle.bat Windows spawn fallback mirroring the pnpm.cmd arm; stale foojay doc note corrected. Windows behavior is CI-verified from here (first run expected to need iteration). Gradle plugin: generated source root now registered via the task provider (srcDir(generateBaml)) so IntelliJ generates on project sync — fresh-clone autocomplete without a first build; TestKit asserts the task-backed source-set edge (4/4). PUBLISHING.md path typo fixed. Local gates: actionlint clean on both workflows, baml_release 9/9, nextest 10/10 (linux battery unchanged), gradle suites green. (SKIP=cargo-clippy: local-nightly artifact; 1.93.0 clippy clean.)
…lization
Two root causes from the first java-leg run (29758639471):
- linux/macos never ran a test: setup.sh sat at index mode 100644
while every sibling setup.sh is 100755 — nextest execs the bare path
and got EACCES. Invisible locally because core.fileMode=false plus a
world-executable working tree. Fixed via git update-index --chmod=+x
(mode-only, blob unchanged).
- windows ran the suite and hit a real race: all fixtures share one
GRADLE_USER_HOME, and even --no-daemon Gradle forks a single-use
daemon whose registry lives under that home — 4 concurrent fixture
javac gates raced the registry ("Cannot start managing file
contention") and llm_functions timed out. The old per-fixture
test-groups serialized within a fixture but not across them.
Replaced the four groups with one package-wide
java-gradle-serial (max-threads=1): the race is platform-agnostic,
windows just surfaces it first.
Local: nextest 10/10 under the serialized config; setup unchanged.
macOS gradle gates get their first true CI run on this push.
(SKIP=cargo-clippy: local-nightly artifact; 1.93.0 clippy clean.)
…entral bundle
The plugin becomes the entire consumer setup:
plugins { id("com.boundaryml.baml") version "X" } now injects the
version-locked com.boundaryml:baml-bridge implementation dep and the
host-detected natives-<platform> runtimeOnly classifier (JavaFX-plugin
pattern; mapping mirrors NativeLibraryLoader). Overrides:
baml { nativePlatforms } (explicit list or "all" — loader picks by
runtime os/arch so extras are inert) and manageDependencies=false; a
pre-existing explicit baml-bridge dep suppresses injection. Injection
is afterEvaluate and declare-only. Plugin version detection works
under TestKit via a generated version resource (resource -> manifest
-> 0.0.0-dev fallback). TestKit 9/9.
Publishing, dual-channel at the family version:
- Portal (stables only): com.gradle.plugin-publish 1.3.1 + metadata,
publish-gradle-plugin job gated channel != nightly, reads
GRADLE_PUBLISH_KEY/SECRET env natively (validate-only verified).
publish-gradle-plugin-manual.yml (workflow_dispatch) fires the
one-time namespace-warming first submission.
- Central (every channel): publish-maven now stages the plugin's
pluginMaven publication AND the auto-created
com.boundaryml.baml.gradle.plugin marker POM into the same signed
bundle, with per-coordinate idempotency (bridge/plugin checked
independently; partial re-publish supported). Staging rehearsal:
11 artifacts, 11 signatures, marker verifies.
Docs: packaging doc gains the dual-channel story + the 3-line
pluginManagement stanza for nightly/pre-approval consumers; plugin
README rewritten. Quickstart stays on explicit deps until the first
plugin version is live (a one-liner example must resolve), flip noted.
Gates: TestKit 9/9, actionlint clean on all three workflows,
publishPlugins --validate-only publishes nothing and validates clean.
(SKIP=cargo-clippy: local-nightly artifact; 1.93.0 clippy clean.)
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 @.github/workflows/build2-java-sdk.reusable.yaml:
- Around line 95-109: Add explicit least-privilege permissions to both the
build-natives and build-main jobs, matching the matrix job’s contents: read
setting. Place each permissions block at the job level so direct
workflow_dispatch runs cannot inherit broader default token permissions.
In @.github/workflows/release-baml-language.yml:
- Around line 687-718: In the “Upload the Central bundle to the portal” step,
validate that CENTRAL_USERNAME and CENTRAL_PASSWORD are non-empty before
constructing token. Emit a clear ::error:: message and exit nonzero when either
secret is missing; only build the Bearer token after both checks pass.
🪄 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: 902bd48f-7403-4cfe-aa47-e3842c9e8454
⛔ Files ignored due to path filters (1)
baml_language/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (13)
.github/workflows/build2-java-sdk.reusable.yaml.github/workflows/cargo-tests.reusable.yaml.github/workflows/publish-gradle-plugin-manual.yml.github/workflows/release-baml-language.ymlbaml_language/.config/nextest.tomlbaml_language/.markdown-whitelistbaml_language/Cargo.tomlbaml_language/crates/baml_cli/Cargo.tomlbaml_language/crates/baml_cli/src/generate.rsbaml_language/crates/baml_codegen_types/src/generator_fields.rsbaml_language/crates/baml_release/src/platforms.rsbaml_language/crates/bridge_cffi/include/baml_cffi.hbaml_language/crates/bridge_cffi/src/ffi/runtime.rs
🚧 Files skipped from review as they are similar to previous changes (4)
- baml_language/crates/baml_cli/Cargo.toml
- baml_language/crates/baml_cli/src/generate.rs
- baml_language/.markdown-whitelist
- baml_language/crates/bridge_cffi/src/ffi/runtime.rs
…ail-fast Both fresh-review findings applied: build-natives/build-main get explicit least-privilege permissions (contents: read) so a direct workflow_dispatch cannot inherit broad default token permissions (zizmor excessive-permissions); publish-maven validates CENTRAL_USERNAME/CENTRAL_PASSWORD with a clear ::error:: before building the Bearer token, matching the file's convention for every other secret. actionlint clean.
Bridge Week: bridge-java
The Java/JVM bridge end to end: Python-parity test harness, the
sdkgen_javaemitter, thebridge_java/baml-bridgeruntime pair, Maven packaging, and the full value-model slice running green against the live engine.Current state
Every capability is implemented and parity-tested:
type_shapes108/108,function_calls157/153/0/4,llm_functions21/21 (streaming e2e on the keyless replay harness) — four fixtures CI-gated (javac + junit) on linux, macOS, and Windows (dedicated sdk-test legs), all compile-exclude lists empty (protobuf encode → JNI → bex_engine → decode). Primitives 14/14, enums 5/5, classes/refs/recursion/routing, lists 5/5, maps 4/4, literals 7/7, unions 6/6, optionals 5/5, generics 6/6 + 2/2, aliases 3/3, forward-refs 4/4, media 9/9, handle shells (baml.fs.File/baml.http.Response) 4/4. Stream partial-values 5/5:$-preserved in-package companions (owner-decided, TS-aligned; the engine accepts host-constructed partials). CI enforces the green set:type_shapes::javacruns live under nextest. CI-enforced runtime gates (nextest runs both suites on every push). Landed today: methods, optional args,@throwsJavadoc, cancellation (trailingBamlCallContextoverloads;BamlCancelledError extends CancellationException;future.cancel(true)reaches the engine), error mapping (TypeMismatch→IllegalArgumentException; BAML frames synthesized into Java stack traces;BamlPanic extends Error). Branch is merged up to current canary; the bridge registers with the versioned C ABI at init (BridgeLanguage::Java = 6, canonical-version validated).Live on Maven Central:
com.boundaryml:baml-bridge:0.15.0-nightly.1— main + natives-linux-x86_64 + sources + javadoc jars, GPG-signed; verified by a clean consumer (purged caches) resolving from Central only and calling the engine through the embedded native. The runtime loads the engine via a property → env → classpath-resource ladder.sdks/java/examples/quickstartdemonstrates the full consumer flow.Gradle plugin (Option C) shipped at
sdks/java/gradle-plugin(com.boundaryml.baml): cacheablegenerateBamlintobuild/generated/sources/baml/java/main, TestKit-verified against the real CLI; Central publish rides the next artifact push.Media: runtime-owned
Image/Audio/Video/Pdfwrapping a realBamlHandle(Cleaner+AutoCloseable; wire keys cloned per send — the engine drains them). Nine JNI exports mint/readAdt(Media)through the same Rust the C-ABIbaml_media_*functions funnel through.Real async:
callAsyncis callback-driven —nativeCallAsyncspawns on the shared tokio runtime and completes the hostCompletableFuturefrom the engine thread (JavaVM + GlobalRef capture,attach_current_thread_as_daemon→ staticcompleteCall); one shareddecodeResultkeeps sync/async ok/error/panic handling identical. Verified against the live engine incl. 16 concurrent calls. Pending map is call-id-keyed and cancellation-ready.Optional args: generated
<ident>$Optsconfigurator + trailingConsumeroverload; untouched fields are omitted from the wire (engine evaluates defaults), touched-null encodes BAMLnull.Reference docs: full 1:1 Python mirror suite
sdks/agent-docs/bridge-ref/now mirrors the Python bridge's five reference docs section-for-section (examples, outbound-decoding, inbound-encoding, type-mappings, completeness + conventions with every rule tagged [decided]/[open]) — 88+ explicit deviation flags, cross-verified against code. Built for the side-by-side decision review.Architecture decisions (docs in
sdks/agent-docs/bridge-ref/)baml_bridge.Union2<A,B>…Union10— sealed, nested recordsArm0..Arm{n-1}in declaration order → exhaustiveswitchon Java 21+ — with type-directed decode: bindings pass typedBamlTypeconstants (the string descriptor grammar and its parser were replaced with structural data in 763a226); arms match against the declared list, never wire order. Wire/proto unchanged. Recursive aliases keep a nominal sealed type (self-reference).Bamlanchor (FQN ↔ class + explicit field order — JVM reflection order is unspecified); classes decode via canonical ctors, encode via public accessors.$-preserved companions; Java-keyword$-escaping (baml_sdk.void$); bytecode as base64 classpath resource (64KB static-init cap).All 283 python parity tests ported 1:1 (byte-identical names); deviations carry
// java-port note:comments.Streaming (landed last)
BamlStream<TPartial,TFinal>wraps the engine's tagged-heap handle;next()/get_final()(+_async) re-enter the engine onbaml.llm.Stream.next/.final; exhaustion returns the stdlibbaml.stream.StreamFinishedvalue (runtime-owned, typemap-registered). The replay harness runs keyless via the new nativeBridgeEnvhook, which also brought the$build_requestapi-key tests live. Async host-callables are parity-tested (future-returning callable, success + exceptional identity round-trip).Landed since the last description update
Explicit generics (named
BamlTypesbag + full token grammar + reified factories +bamlTypeArgs()readback), host callables (Java-side registry, dedicated dispatch executor, exception identity, generated callback interfaces), type-faithful union arm selection (wireitem_typeconsumed; empty-list contract pinned), canary merge (C++/Go bridges; Java =BridgeLanguage7), and the full CodeRabbit sweep (29 comments: 17 fixes incl. the double-equals contract and the missing runtime-init trigger; stale/disputed answered inline).Release pipeline + distribution (armed, activates on merge)
Full 8-target native matrix (
platforms.jsonjava key, panic-unwindrelease-bridge-javaprofile), automated GPG-signed Maven Central publishing (publishingType=AUTOMATIC, per-coordinate idempotency) on every family release; Gradle plugin ships dual-channel (Portal stables viapublish-gradle-plugin, Central nightlies in the same bundle incl. the marker POM) with the one-liner consumer UX —plugins { id("com.boundaryml.baml") }injects the version-locked runtime + host-detected natives. All six credential secrets set. First Portal publish awaits the one-time namespace approval (warming workflow ready).Known follow-ups
Team-parked union items (arity>10 policy /
max_typed_union_arity, arm-order canonicalization, literal-arm erasure, selected-arm ABI metadata — see the decisions doc), Gradle-plugin Central publish + plugin-based quickstart, full 8-target CI matrix with a release owner, GMM variant auto-selection. Size Gate red is inherited canary baseline drift (bridge_wasm +130KB fails identically on canary's tip; their refresh job owns it). Upstream flag for other static bridges:maybe_wrap_uniondoesn't deref recursive aliases (Java sidesteps via type-directed decode; Go/C# will hit it).will hit it).
Summary by CodeRabbit
New Features
Documentation
Tests