Skip to content

feat: AgentControl data model, parsing & interpolation (AIC-2662)#171

Open
ctawiah wants to merge 5 commits into
mainfrom
feat/AIC-2662/ai-sdk-datamodel
Open

feat: AgentControl data model, parsing & interpolation (AIC-2662)#171
ctawiah wants to merge 5 commits into
mainfrom
feat/AIC-2662/ai-sdk-datamodel

Conversation

@ctawiah

@ctawiah ctawiah commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Requirements

  • I have added test coverage for new or changed functionality
  • I have followed the repository's pull request submission guidelines
  • I have validated my changes against all supported platform versions

Related issues

Describe the solution you've provided

Step 2 of the Java AI SDK: the AICONF data model and the defensive JSON-protocol parsing layer. No LDAIClient methods, tracker, or evaluation (those are AIC-2663+).

Scope note: Mustache interpolation has been pulled out of this PR. Per the SDK team's supply-chain guidance we will not link the external com.samskivert:jmustache artifact. The Mustache engine will be vendored (source copied into an internal, relocated package) together with the Interpolator in AIC-2695, which gates the v1.0 release (AIC-2666). This PR therefore has no third-party templating dependency.

Public data modelcom.launchdarkly.sdk.server.ai.datamodel (immutable, builder-based, documented):

  • LDMessage (Role enum + content), ModelConfig (name/parameters/custom), ProviderConfig, ToolConfig (root-level tool), JudgeConfiguration (+ nested Judge), and the AIConfigMode enum.

Internal parsingcom.launchdarkly.sdk.server.ai.internal (excluded from published Javadoc/sources):

  • LDValueConverter — depth-capped (MAX_DEPTH = 100) LDValue → plain Java tree. Integral numbers within ±2^53 decode to Long, otherwise Double.
  • AIConfigParser + AIConfigFlagValueLDValue → strongly-typed parse. Malformed/missing/wrong-typed fields never throw; tools fall back to model.parameters.tools[]; evaluationMetricKey resolves to the first non-blank of evaluationMetricKey / evaluationMetricKeys[]; _ldMeta scalars are boxed to preserve tri-state enabled (absent ≠ false).

Design decisions (documented in code)

  • Data-model placement: kept in ...server.ai.datamodel rather than extracting a shared module now, to avoid premature abstraction; can relocate if a client-side AI SDK ever needs it.
  • Boxed vs primitive: _ldMeta scalars (enabled/version) are boxed in the parsed value so "absent" is distinguishable from a concrete value; resolved configs (later step) will use primitives with documented defaults.
  • Number precision: whole numbers outside ±2^53 cannot be represented exactly and decode to the nearest Double.

Additional context

Field shapes and behaviors mirror the Python (python-server-sdk-ai) and JS (js-core/packages/sdk/server-ai) reference SDKs for parity. 22 unit tests cover defensive parsing fallbacks and number/tool/metric-key resolution; full module build (compile + checkstyle main/test + javadoc + tests) is green.


Note

Low Risk
New library surface and internal parsers only; no client evaluation or runtime wiring yet, with broad unit test coverage for malformed payloads.

Overview
Adds the Java server AI SDK data model and defensive flag-variation parsing for AI Configs (AIC-2662). Mustache interpolation is not in this PR; build.gradle documents vendoring Mustache in AIC-2695 instead of linking jmustache.

Public API — New LDAIConfigTypes in datamodel groups immutable nested types: Mode, Message (+ Role), Model, Provider, Tool, and JudgeConfiguration (+ Judge), with builders where needed and Message.withContent reserved for future interpolation.

Internal parsingAIConfigParser maps LDValueAIConfigFlagValue without throwing on bad input; LDValueConverter turns nested JSON into plain Java maps/lists with depth cap (MAX_DEPTH = 100) and integral-number handling within ±2^53. Parsing covers _ldMeta (boxed enabled for absent vs false), tools (root tools over model.parameters.tools[]), and evaluation metric key (scalar then first non-blank in evaluationMetricKeys[]).

Build — Javadoc no longer sets failOnError = false now that public types exist; empty sonatype* entries removed from gradle.properties. Unit tests cover parser and converter edge cases.

Reviewed by Cursor Bugbot for commit 69416f0. Bugbot is set up for automated code reviews on this repo. Configure here.

@ctawiah ctawiah changed the title feat: AI Config data model, parsing & interpolation (AIC-2662) feat: AgentControl data model, parsing & interpolation (AIC-2662) Jun 5, 2026
Base automatically changed from feat/AIC-2661/ai-sdk-module-foundation to main June 8, 2026 14:59
ctawiah and others added 3 commits June 8, 2026 11:40
…ation (AIC-2662)

Implements Step 2 of the Java AI SDK: the AICONF data model plus the
JSON-protocol parsing and interpolation layers. No client methods,
tracker, or evaluation are included (those are later steps).

Public data model (com.launchdarkly.sdk.server.ai.datamodel):
- LDMessage (role enum + content), ModelConfig, ProviderConfig,
  ToolConfig, JudgeConfiguration (+ nested Judge), AIConfigMode.
- Immutable, builder-based, documented public types.

Internal parsing/interpolation (com.launchdarkly.sdk.server.ai.internal):
- LDValueConverter: depth-capped LDValue -> plain Java tree. Integral
  numbers within +/-2^53 decode to Long, otherwise Double (precision
  beyond 2^53 is documented).
- AIConfigParser + AIConfigFlagValue: defensive LDValue -> typed parse.
  Malformed/wrong-typed fields never throw; tools fall back to
  model.parameters.tools[]; evaluationMetricKey resolves to the first
  non-blank of evaluationMetricKey / evaluationMetricKeys[].
- Interpolator: jmustache engine matching the JS/Python escaping policy
  (no HTML escaping, missing/null -> ""), with ldctx merged last so it
  overrides any caller-supplied ldctx, and a thread-safe compiled-template
  cache.

Build:
- Re-add the (now audited) com.samskivert:jmustache:1.16 dependency as
  implementation scope; flip javadoc failOnError back on now that public
  types exist.

Tests: 32 unit tests covering defensive parsing fallbacks, number/tool/
metric-key resolution, tri-state enabled, and interpolation parity
(escaping, missing-variable, ldctx-wins).

Co-authored-by: Cursor <cursoragent@cursor.com>
jmustache 1.16 is compiled for Java 9 (class file version 53.0) and throws
UnsupportedClassVersionError on the Java 8 runtimes this SDK supports, which
surfaced as JDK 8 test failures in CI. 1.15 is the last release compiled for
Java (bytecode major 51) and exposes the same compiler API we use
(escapeHTML / defaultValue), with no transitive dependencies.

Co-authored-by: Cursor <cursoragent@cursor.com>
…C-2695 (AIC-2662)

Per the SDK team's supply-chain guidance we will not link the external
com.samskivert:jmustache artifact. Drop the dependency and the Interpolator
(plus its tests) from this PR; the Mustache engine will be vendored (source
copied into an internal, relocated package) together with the Interpolator in
AIC-2695, which gates the v1.0 release.

This PR now ships only the data model and the defensive LDValue parsing layer,
neither of which depends on a templating engine.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ctawiah ctawiah force-pushed the feat/AIC-2662/ai-sdk-datamodel branch from 3ed2dc5 to 47ceb0f Compare June 8, 2026 15:41
…ties (AIC-2662)

These empty sonatypeUsername/sonatypePassword placeholders aren't needed; the
core SDK module (lib/sdk/server) carries none and publishes fine. The nexus
publish plugin resolves credentials from the environment at release time.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ctawiah ctawiah marked this pull request as ready for review June 8, 2026 15:59
@ctawiah ctawiah requested a review from a team as a code owner June 8, 2026 15:59

@jsonbailey jsonbailey left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved from me but would be good to also get Todds review.

@mattrmc1 mattrmc1 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: For the .NET SDK, we opted to organize shared shapes like Message, Model, Provider, Tools, etc into a static class LdAiConfigTypes. This reduces the number of files and frees up generic class names (ie Message) within the namespace. Could do the same thing here for consistency

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it the case that none of the enums added in this PR are for public consumers to switch over? The reason I bring that up is adding another value to an enum is breaking in java.

@ctawiah ctawiah Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a high probably that we can introduce a new enum for the mode in the future. The mode is mainly for internal use and theres no plans to expose that to customers.

@tanderson-ld tanderson-ld self-requested a review June 9, 2026 19:15
Group the shared AI Config shapes (Mode, Message, Model, Provider, Tool,
JudgeConfiguration) as nested types under a single non-instantiable
LDAIConfigTypes container instead of separate top-level classes. This
reduces the file count and frees up generic names like Message and Model
within the datamodel package, matching the organization used by the
.NET SDK (LdAiConfigTypes). Per Matt's review on #171.

Internal references (AIConfigParser, AIConfigFlagValue) and tests are
updated to the nested names; behavior is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ctawiah ctawiah requested a review from tanderson-ld June 10, 2026 23:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants