feat: vendor Mustache templating engine & add Interpolator (AIC-2695)#172
feat: vendor Mustache templating engine & add Interpolator (AIC-2695)#172ctawiah wants to merge 7 commits into
Conversation
| @@ -0,0 +1,43 @@ | |||
| This product includes vendored third-party source code. The relevant licenses | |||
There was a problem hiding this comment.
This was only added to give attribution to the vendored lib but if theres existing attribution process, I'm happy to switch to that.
| if (context == null || !context.isValid()) { | ||
| return new HashMap<>(); | ||
| } | ||
| LDValue asValue = LDValue.parse(JsonSerialization.serialize(context)); |
There was a problem hiding this comment.
We shouldn't need to go through serialization to get to a map. That's not very efficient. This should probably be something that walks the graph. At the leafs you can use your LDValueConverter in the target PR.
There was a problem hiding this comment.
Consider putting this as a utility in common package next to the context implementation.
There was a problem hiding this comment.
On serialization: I've updated the logic so it no longer round-trips through serialize/parse.
On the common utility: agreed, the context-encoding logic belongs in common. Since we already have a follow-up ticket to move LDValueConverter into common (AIC-2715), I've folded this into it so both move together when we update common.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit b8778a2. Configure here.
b8778a2 to
97d46c1
Compare
| * {@code ldctx}, without round-tripping through JSON serialization. A single-kind context becomes | ||
| * a map of its attributes; a multi-kind context becomes {@code {"kind":"multi", <kind>: {...}}} | ||
| * with one nested map per individual context. | ||
| */ |
There was a problem hiding this comment.
Had AI double check this against other impls. Looks like .NET always emits anonymous true/false and not only when true. Also .NET puts the fully qualified key next to Multi at the top level.
There was a problem hiding this comment.
Also need to discuss with SDK team if private attributes should be going into the templating space. That will happen in a few hours.
There was a problem hiding this comment.
@tanderson-ld what decision came out of the discussion with the SDK team
There was a problem hiding this comment.
Good to template with those attributes. Did the .NET disparity get investigated / addressed?
There was a problem hiding this comment.
Yep, investigated it against the .NET source (ConfigFactory.cs) and addressed both items in this PR - good catch
Vendor the com.samskivert:jmustache:1.15 source into the relocated internal package com.launchdarkly.sdk.server.ai.internal.mustache instead of linking the external artifact, per the SDK team's supply-chain guidance. The library is now compiled from source and ships inside this jar with no third-party runtime dependency, and compiles to Java 8 bytecode against our target (avoiding the class-version mismatch the external jar hit on JDK 8). Also (re)adds the internal Interpolator, which renders AI Config message and instruction templates using the cross-SDK policy (no HTML escaping, missing/null render empty, reserved ldctx wins) and caches compiled templates. - Vendored source kept byte-for-byte from upstream aside from a provenance banner and the relocated package declaration. - THIRD-PARTY-NOTICES.txt records the upstream BSD 3-Clause license. - Vendored package excluded from checkstyle; internal package already excluded from the published javadoc/sources jars. Co-authored-by: Cursor <cursoragent@cursor.com>
Build the ldctx template variable by walking the LDContext's attributes
directly instead of serializing it to a JSON string and parsing it back
into an LDValue. This removes the serialize-then-deserialize round trip
flagged in review, following the generic-encoder shape used by the iOS
SDK. Custom attribute values are still converted via LDValueConverter so
nested objects/arrays remain addressable, and multi-kind contexts are
exposed as {"kind":"multi", <kind>: {...}}.
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…ts (AIC-2695)
Match LaunchDarkly's standard context JSON, where per-kind objects under a
multi-kind context omit "kind" (it is implied by the property key). Keeps
{{ldctx.<kind>.kind}} consistent with the JS and Python SDKs.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Always expose ldctx.anonymous as true/false rather than only when true.
- Expose the canonical fully-qualified key at the top level of a multi-kind
ldctx so {{ldctx.key}} resolves, matching the .NET (and other) SDKs.
Adds InterpolatorTest cases for both.
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…C-2695) Co-authored-by: Cursor <cursoragent@cursor.com>
68a0c99 to
b1b318e
Compare

Requirements
Related issues
AIC-2695 — Vendor the Mustache templating library + add the Interpolator. Part of epic AIC-2629.
Describe the solution you've provided
com.launchdarkly.sdk.server.ai.internal.mustache(7 files). Kept byte-for-byte from upstream1.15aside from a provenance banner and the relocatedpackagedeclaration. It is now compiled from source and ships inside this jar with no third-party runtime dependency.targetCompatibility = 1.8, the vendored classes are emitted as class-version 52 (Java 8). This sidesteps theUnsupportedClassVersionErrorthe external1.16jar hit on JDK 8.Interpolator(internal) renders AI Config message/instruction templates using the cross-SDK policy shared with JS/Python: no HTML escaping, missing/null variables render empty, and the reservedldctxvariable (derived from the eval context) always wins. Compiled templates are cached in aConcurrentHashMap; the compiler and compiledTemplates are immutable/thread-safe.THIRD-PARTY-NOTICES.txtrecords the upstream BSD 3-Clause license (Copyright (c) 2010 Michael Bayne).internal/**exclusion for the published javadoc/sources jars. Verified the main jar contains the compiled vendored classes while the sources jar excludes all internal code.Validated with
./gradlew clean build(checkstyle + all unit tests, including the 10InterpolatorTestparity cases, green).Describe alternatives you've considered
Additional context
This must land before the v1.0 release (AIC-2666).
Made with Cursor
Note
Medium Risk
Large in-tree third-party surface and template rendering will affect AI prompt text once wired, though changes stay internal with tests; supply-chain risk is reduced by removing the external artifact.
Overview
Vendors JMustache 1.15 into
com.launchdarkly.sdk.server.ai.internal.mustacheso AI Config templating ships in the jar with nocom.samskivert:jmustacheruntime dependency, and documents the BSD license inTHIRD-PARTY-NOTICES.txt.Adds an internal
Interpolatorthat renders Mustache templates for AI messages/instructions using the cross-SDK rules: no HTML escaping, missing/null → empty string, andldctxbuilt fromLDContext(including multi-kind layout) merged last so it overrides caller-suppliedldctx. Templates are compiled once and cached in aConcurrentHashMap.build.gradleupdates the Mustache supply-chain note and excludes the vendored package fromcheckstyleMain.InterpolatorTestlocks parity behavior (escaping,ldctx, nesting, cache).Reviewed by Cursor Bugbot for commit 04326f0. Bugbot is set up for automated code reviews on this repo. Configure here.