Skip to content

feat: AICONF config types & LDAIClient methods (AIC-2663)#173

Open
ctawiah wants to merge 3 commits into
feat/AIC-2695/ai-sdk-vendor-mustachefrom
feat/AIC-2663/ai-sdk-client
Open

feat: AICONF config types & LDAIClient methods (AIC-2663)#173
ctawiah wants to merge 3 commits into
feat/AIC-2695/ai-sdk-vendor-mustachefrom
feat/AIC-2663/ai-sdk-client

Conversation

@ctawiah

@ctawiah ctawiah commented Jun 9, 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

AIC-2663 — Step 3: AICONF config types & LDAIClient methods. Part of epic AIC-2629.

Stacked on #172 (feat/AIC-2695/ai-sdk-vendor-mustache), which is stacked on #171. It builds on the data model + parser (#171) and the vendored-Mustache Interpolator (#172). Base retargets up the stack as each merges.

Describe the solution you've provided

Implements the public AI Config types and the LDAIClient retrieval methods that evaluate a flag, validate its mode, interpolate prompt templates, and return a typed config. The behavior mirrors the JS reference the spec points to.

  • Config types.NET-style two-hierarchy design (AIConfig / AIConfigDefault bases): AICompletionConfig(+messages), AIAgentConfig(+instructions), AIJudgeConfig(+evaluationMetricKey) result types plus parallel *Default builder types, and AIAgentConfigRequest for batch agent retrieval. A generic base builder keeps the *Default builders DRY.
  • LDAIClient methodscompletionConfig, agentConfig, agentConfigs, judgeConfig:
    • serialize the caller default to a flag value (tagged with the requested mode) and pass it through jsonValueVariation, so an absent flag yields the default and the eval event records the correct default;
    • mode validation: a mismatch logs a single warning and returns a disabled config of the requested type — never a config that would NPE the caller;
    • interpolate messages/instructions via the vendored-Mustache Interpolator, exposing the context as {{ldctx}};
    • fire the spec'd usage events ($ld:ai:usage:completion-config, :agent-config, :agent-configs, :judge-config) and emit $ld:ai:sdk:info once at construction, guarded so an uninitialized client can't throw from the constructor.
  • evaluationMetricKey resolves to the first non-blank entry (handled by the feat: AgentControl data model, parsing & interpolation (AIC-2662) #171 parser).

Key decisions

  • Synchronous API (no CompletableFuture). Matches the core Java server SDK's blocking style and sidesteps Android API-level/threading concerns. variation is in-memory after init, so agentConfigs fan-out parallelism buys ~nothing while adding real complexity. Resolves the ticket's async-surface question.
  • No per-field merge of missing model/provider/instructions from the default (matches JS).
  • Tracking deferred to Step 4 (AIC-2664). LDAIConfigTracker is a placeholder interface with an internal no-op; configs expose createTracker() so Step 4 fills in behavior without reshaping the public config types.
  • Construction: new LDAIClientImpl(ldClient) (interface LDAIClient is provided for mocking/DI). A second constructor accepts an LDLogger.

Thread-safetyLDAIClientImpl holds only the thread-safe base client, a logger, and one shared Interpolator (thread-safe template cache); every returned config is immutable.

TestsLDAIClientImplTest (17 cases) covers usage events, SDK-info emission + constructor guard, typed retrieval, interpolation/ldctx, mode-mismatch (disabled + single warning, no NPE), default semantics (absent → default; no per-field merge), and agentConfigs ordering/count. LDValueConverterTest extended for the new inverse conversion. ./gradlew clean build green (checkstyle + Javadoc + all tests).

Describe alternatives you've considered

  • Single unified config type (default == result) — rejected; the ticket and sibling SDKs intentionally separate the caller-supplied default from the retrieved result (which carries the key + tracker + interpolated content).
  • Async CompletableFuture surface — rejected for a server SDK (see decision above).

Additional context

AISdkInfo.VERSION is currently a constant kept in step with gradle.properties; wiring it to a generated/build-time version can be a follow-up.

Made with Cursor


Note

Medium Risk
New public SDK API on the flag-evaluation path; incorrect defaults, mode handling, or interpolation would affect how apps load AI configs in production, though scope is additive with guarded telemetry.

Overview
Adds the public AI Config retrieval surface for the Java server AI SDK: immutable AICompletionConfig, AIAgentConfig, and AIJudgeConfig types (plus matching *Default builders and AIAgentConfigRequest for batch agents), wired through LDAIClient / LDAIClientImpl.

LDAIClientImpl evaluates flags via jsonValueVariation with a null sentinel, parses variations, validates mode (mismatch → disabled config + warning), and interpolates messages/instructions (including {{ldctx}}). Absent flags return the caller default with interpolation and no per-field merge from defaults when a variation is present. It emits $ld:ai:sdk:info at construction (failure-safe) and $ld:ai:usage:* metrics per method; LDAIConfigTracker is a placeholder with a no-op implementation until a later step.

The README now documents constructing LDAIClientImpl from LDClient and calling completionConfig. LDAIClientImplTest covers usage events, interpolation, mode mismatch, default semantics, and ordered agentConfigs.

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

@ctawiah ctawiah force-pushed the feat/AIC-2695/ai-sdk-vendor-mustache branch from b8778a2 to 97d46c1 Compare June 10, 2026 23:43
@ctawiah ctawiah force-pushed the feat/AIC-2663/ai-sdk-client branch from 7ab90bf to 72fc433 Compare June 10, 2026 23:46
ctawiah and others added 3 commits June 10, 2026 21:49
Implement the public AI Config types and the LDAIClient retrieval methods that
evaluate a flag, validate its mode, interpolate prompt templates, and return a
typed config.

Config types follow the .NET-style two-hierarchy design (AIConfig /
AIConfigDefault bases): AICompletionConfig(+messages), AIAgentConfig
(+instructions), AIJudgeConfig(+evaluationMetricKey) results plus parallel
*Default builder types (a generic base builder keeps the defaults DRY), and
AIAgentConfigRequest for batch agent retrieval.

LDAIClientImpl mirrors the JS reference the spec points to:
- serialize the caller default to a flag value (with the requested mode) and
  pass it through jsonValueVariation, so an absent flag yields the default and
  the eval event records the correct default;
- validate mode: a mismatch logs a single warning and returns a disabled config
  of the requested type (never a config that would NPE the caller);
- interpolate messages/instructions via the vendored-Mustache Interpolator,
  exposing the context as {{ldctx}};
- fire the spec'd usage events ($ld:ai:usage:completion-config, :agent-config,
  :agent-configs, :judge-config) and emit $ld:ai:sdk:info once at construction,
  guarded so an uninitialized client can't throw from the constructor.

Design decisions (documented in the PR):
- Synchronous API (no CompletableFuture): matches the core Java server SDK and
  avoids Android-API/threading concerns; variation is in-memory post-init so
  agentConfigs fan-out parallelism buys ~nothing.
- No per-field merge of missing fields from the default (matches JS).
- Tracking is deferred to Step 4 (AIC-2664): LDAIConfigTracker is a placeholder
  interface with an internal no-op; configs expose createTracker() so Step 4
  fills in behavior without reshaping the public types.

Adds LDValueConverter.fromJavaObject (inverse conversion for default
serialization). LDAIClientImplTest covers usage events, typed retrieval,
interpolation/ldctx, mode-mismatch, default semantics, and agentConfigs.

Co-authored-by: Cursor <cursoragent@cursor.com>
…IC-2663)

When a flag is absent or unevaluable, build the typed AIConfig straight from
the caller's default rather than serializing the default to LDValue and parsing
it back. Drops the now-unused LDValueConverter.fromJavaObject helpers.

Co-authored-by: Cursor <cursoragent@cursor.com>
Update the AIConfig hierarchy, LDAIClientImpl, and tests to reference the
consolidated LDAIConfigTypes.{Mode,Message,Model,Provider,Tool,JudgeConfiguration}
types introduced on the data-model PR.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ctawiah ctawiah force-pushed the feat/AIC-2663/ai-sdk-client branch from 72fc433 to ac6b827 Compare June 11, 2026 01:49
@ctawiah ctawiah marked this pull request as ready for review June 11, 2026 01:53
@ctawiah ctawiah requested a review from a team as a code owner June 11, 2026 01:53

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ 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 ac6b827. Configure here.

AIConfigDefault defaultValue,
Mode mode,
Map<String, Object> variables) {
LDValue value = client.jsonValueVariation(key, context, LDValue.ofNull());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Eval events ignore caller defaults

Medium Severity

evaluate always passes LDValue.ofNull() into jsonValueVariation, but the server SDK attaches that argument to flag evaluation events as the recorded fallback default. Callers still receive their typed *Default via buildConfigFromDefault, so runtime behavior can be correct while every AI config evaluation is attributed with a null default instead of the serialized default (with the requested mode) described for this step.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ac6b827. Configure here.

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.

1 participant