launchdarkly-server-sdk-ai 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 841cd2b74f8a6e89125e283d2c75442ccb58d64373f04439f882b9d968803818
4
- data.tar.gz: 85a82738b8c931e8a9dc466a9cc66ec077e20a2e58fb112e01cd286b1bf551df
3
+ metadata.gz: e1bdaa1e6cd790510ce57f6e0d63b3719f571ec7b5299cecc430995158240897
4
+ data.tar.gz: bca165be2cc34d56f742d1d36ed89f199ba9a48c6d9e9f9fea88d1397d5af89f
5
5
  SHA512:
6
- metadata.gz: 1fb7745e91ce1fb85b965c72f033d3c6ea8978f47d30aa691502251bba83238fbe279d5b67cc3a4e00b8669f3509bf61591bba7fa865f3147db2afc9cb5d3361
7
- data.tar.gz: dde93cdaac0a5cf24e6c684d9b1769f9c5e6092e1140bf6f8125e6bc8a2eb0c485cc67f460b195366473f8ca2cda6ba41618d57e2c8e08e73798613c590901fa
6
+ metadata.gz: 3586d99728f38b474dce7a35d9721a1871610f65eb1336070cce5bad4dbff557181c4956fb25e4b6d5230e18fe0da5c43281164bdb387de4a98d3d4b3d42f09c
7
+ data.tar.gz: 1f80b447eb3f615380b353ec87b88fb2c997e332a6515adf60bb9322d3d76a625e4a4f061ca536c1a19ecca052d67e90a433df67cd717ad3836d14c5a7dfd8fe
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.0](https://github.com/launchdarkly/ruby-server-sdk-ai/compare/0.3.0...0.4.0) (2026-05-15)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * Replace tracker tuple from completion_config with AIConfig#create_tracker factory
9
+ * Track each AIConfigTracker metric at most once per tracker
10
+ * Add per-execution runId, at-most-once tracking, and cross-process tracker resumption
11
+
12
+ ### Features
13
+
14
+ * Add Client#create_tracker(token:, context:) to resume a tracker across processes ([20f06f1](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/20f06f1f24a42692953ae44cebea70aaa416b29d))
15
+ * Add per-execution runId to correlate AIConfigTracker events ([20f06f1](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/20f06f1f24a42692953ae44cebea70aaa416b29d))
16
+ * Add per-execution runId, at-most-once tracking, and cross-process tracker resumption ([20f06f1](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/20f06f1f24a42692953ae44cebea70aaa416b29d))
17
+ * Replace tracker tuple from completion_config with AIConfig#create_tracker factory ([20f06f1](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/20f06f1f24a42692953ae44cebea70aaa416b29d))
18
+ * Track each AIConfigTracker metric at most once per tracker ([20f06f1](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/20f06f1f24a42692953ae44cebea70aaa416b29d))
19
+
3
20
  ## [0.3.0](https://github.com/launchdarkly/ruby-server-sdk-ai/compare/0.2.2...0.3.0) (2026-03-05)
4
21
 
5
22
 
data/PROVENANCE.md CHANGED
@@ -1,15 +1,15 @@
1
- ## Verifying SDK build provenance with the SLSA framework
1
+ ## Verifying SDK build provenance with GitHub artifact attestations
2
2
 
3
- LaunchDarkly uses the [SLSA framework](https://slsa.dev/spec/v1.0/about) (Supply-chain Levels for Software Artifacts) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages.
3
+ LaunchDarkly uses [GitHub artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages.
4
4
 
5
- As part of [SLSA requirements for level 3 compliance](https://slsa.dev/spec/v1.0/requirements), LaunchDarkly publishes provenance about our SDK package builds using [GitHub's generic SLSA3 provenance generator](https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/generic/README.md#generation-of-slsa3-provenance-for-arbitrary-projects) for distribution alongside our packages. These attestations are available for download from the GitHub release page for the release version under Assets > `multiple-provenance.intoto.jsonl`.
5
+ LaunchDarkly publishes provenance about our SDK package builds using [GitHub's `actions/attest` action](https://github.com/actions/attest). These attestations are stored in GitHub's attestation API and can be verified using the [GitHub CLI](https://cli.github.com/).
6
6
 
7
- To verify SLSA provenance attestations, we recommend using [slsa-verifier](https://github.com/slsa-framework/slsa-verifier). Example usage for verifying SDK packages is included below:
7
+ To verify build provenance attestations, we recommend using the [GitHub CLI `attestation verify` command](https://cli.github.com/manual/gh_attestation_verify). Example usage for verifying SDK packages is included below:
8
8
 
9
9
  <!-- x-release-please-start-version -->
10
10
  ```
11
- # Set the version of the SDK to verify
12
- VERSION=0.3.0
11
+ # Set the version of the library to verify
12
+ VERSION=0.4.0
13
13
  ```
14
14
  <!-- x-release-please-end -->
15
15
 
@@ -17,27 +17,33 @@ VERSION=0.3.0
17
17
  # Download gem
18
18
  $ gem fetch launchdarkly-server-sdk-ai -v $VERSION
19
19
 
20
- # Download provenance from Github release
21
- $ curl --location -O \
22
- https://github.com/launchdarkly/ruby-server-sdk-ai/releases/download/${VERSION}/launchdarkly-server-sdk-ai-${VERSION}.gem.intoto.jsonl
23
-
24
- # Run slsa-verifier to verify provenance against package artifacts
25
- $ slsa-verifier verify-artifact \
26
- --provenance-path launchdarkly-server-sdk-ai-${VERSION}.gem.intoto.jsonl \
27
- --source-uri github.com/launchdarkly/ruby-server-sdk-ai \
28
- launchdarkly-server-sdk-ai-${VERSION}.gem
20
+ # Verify provenance using the GitHub CLI
21
+ $ gh attestation verify launchdarkly-server-sdk-ai-${VERSION}.gem --owner launchdarkly
29
22
  ```
30
23
 
31
24
  Below is a sample of expected output.
32
- TODO: Verify these are accurate
25
+
33
26
  ```
34
- Verified signature against tlog entry index 83653185 at URL: https://rekor.sigstore.dev/api/v1/log/entries/24296fb24b8ad77a7df0bbf87a7d5fcaafa551a2101d9f993d251a56a918bb113e81d2c575dc7e25
35
- Verified build using builder "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@refs/tags/v1.10.0" at commit 14c48a68c45871c27409591969e7f4c0ebdcdf62
36
- Verifying artifact launchdarkly-server-sdk-ai-1.0.0.gem: PASSED
27
+ Loaded digest sha256:... for file://launchdarkly-server-sdk-ai-0.3.0.gem
28
+ Loaded 1 attestation from GitHub API
29
+
30
+ The following policy criteria will be enforced:
31
+ - Predicate type must match:................ https://slsa.dev/provenance/v1
32
+ - Source Repository Owner URI must match:... https://github.com/launchdarkly
33
+ - Subject Alternative Name must match regex: (?i)^https://github.com/launchdarkly/
34
+ - OIDC Issuer must match:................... https://token.actions.githubusercontent.com
35
+
36
+ ✓ Verification succeeded!
37
+
38
+ The following 1 attestation matched the policy criteria
37
39
 
38
- PASSED: Verified SLSA provenance
40
+ - Attestation #1
41
+ - Build repo:..... launchdarkly/ruby-server-sdk-ai
42
+ - Build workflow:. .github/workflows/release-please.yml
43
+ - Signer repo:.... launchdarkly/ruby-server-sdk-ai
44
+ - Signer workflow: .github/workflows/release-please.yml
39
45
  ```
40
46
 
41
- Alternatively, to verify the provenance manually, the SLSA framework specifies [recommendations for verifying build artifacts](https://slsa.dev/spec/v1.0/verifying-artifacts) in their documentation.
47
+ For more information, see [GitHub's documentation on verifying artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds#verifying-artifact-attestations-with-the-github-cli).
42
48
 
43
- **Note:** These instructions do not apply when building our libraries from source.
49
+ **Note:** These instructions do not apply when building our libraries from source.
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'base64'
4
+ require 'json'
3
5
  require 'ldclient-rb'
4
6
 
5
7
  module LaunchDarkly
@@ -39,7 +41,14 @@ module LaunchDarkly
39
41
  end
40
42
 
41
43
  #
42
- # The AIConfigTracker class is used to track AI configuration usage.
44
+ # The AIConfigTracker records metrics for a single AI run. Unless
45
+ # otherwise noted, the tracker's methods are not safe for concurrent use.
46
+ #
47
+ # All events a tracker emits share a runId (a UUIDv4) so LaunchDarkly can
48
+ # correlate them in metrics views. See individual track methods for their
49
+ # specific semantics. Call create_tracker on the AI Config to start a new
50
+ # run. A resumption token preserves the runId, so events emitted by a
51
+ # tracker reconstructed in another process share the original runId.
43
52
  #
44
53
  class AIConfigTracker
45
54
  attr_reader :ld_client, :config_key, :context, :variation_key, :version, :summary, :model_name, :provider_name
@@ -55,7 +64,7 @@ module LaunchDarkly
55
64
  # @param provider_name [String] The name of the AI provider
56
65
  # @param context [LDContext] The context used for the flag evaluation
57
66
  #
58
- def initialize(ld_client:, variation_key:, config_key:, version:, model_name:, provider_name:, context:)
67
+ def initialize(ld_client:, run_id:, config_key:, variation_key:, version:, context:, model_name:, provider_name:)
59
68
  @ld_client = ld_client
60
69
  @variation_key = variation_key
61
70
  @config_key = config_key
@@ -64,14 +73,62 @@ module LaunchDarkly
64
73
  @provider_name = provider_name
65
74
  @context = context
66
75
  @summary = MetricSummary.new
76
+ @run_id = run_id
77
+ @logger = LaunchDarkly::Server::AI.default_logger
78
+ end
79
+
80
+ #
81
+ # Returns a URL-safe Base64-encoded JSON token that can be used to reconstruct
82
+ # a tracker in a different process (e.g. for deferred feedback).
83
+ #
84
+ # The token contains: runId, configKey, variationKey, version.
85
+ # modelName and providerName are NOT included.
86
+ #
87
+ # @return [String] the resumption token
88
+ #
89
+ def resumption_token
90
+ payload = { runId: @run_id, configKey: @config_key }
91
+ payload[:variationKey] = @variation_key if @variation_key && !@variation_key.empty?
92
+ payload[:version] = @version
93
+ Base64.urlsafe_encode64(JSON.generate(payload), padding: false)
94
+ end
95
+
96
+ #
97
+ # Reconstructs a tracker from a resumption token.
98
+ #
99
+ # @param token [String] A URL-safe Base64-encoded JSON resumption token
100
+ # @param ld_client [LDClient] The LaunchDarkly client instance
101
+ # @param context [LDContext] The context for track events
102
+ # @return [AIConfigTracker] A new tracker instance
103
+ #
104
+ def self.from_resumption_token(token:, ld_client:, context:)
105
+ json = Base64.urlsafe_decode64(token)
106
+ payload = JSON.parse(json)
107
+
108
+ new(
109
+ ld_client: ld_client,
110
+ run_id: payload['runId'],
111
+ config_key: payload['configKey'],
112
+ variation_key: payload.fetch('variationKey', ''),
113
+ version: payload['version'],
114
+ context: context,
115
+ model_name: '',
116
+ provider_name: ''
117
+ )
67
118
  end
68
119
 
69
120
  #
70
- # Track the duration of an AI operation
121
+ # Track the duration of an AI run.
122
+ #
123
+ # Records at most once per Tracker; further calls are ignored.
71
124
  #
72
125
  # @param duration [Integer] The duration in milliseconds
73
126
  #
74
127
  def track_duration(duration)
128
+ unless @summary.duration.nil?
129
+ @logger&.warn("Skipping track_duration: duration already recorded on this tracker. Call create_tracker on the AI Config for a new run. #{flag_data}")
130
+ return
131
+ end
75
132
  @summary.duration = duration
76
133
  @ld_client.track(
77
134
  '$ld:ai:duration:total',
@@ -96,11 +153,18 @@ module LaunchDarkly
96
153
  end
97
154
 
98
155
  #
99
- # Track time to first token
156
+ # Track time to first token.
157
+ #
158
+ # Records at most once per Tracker; further calls are ignored.
100
159
  #
101
160
  # @param duration [Integer] The duration in milliseconds
102
161
  #
103
162
  def track_time_to_first_token(time_to_first_token)
163
+ unless @summary.time_to_first_token.nil?
164
+ @logger&.warn("Skipping track_time_to_first_token: time-to-first-token already recorded on this tracker. " \
165
+ "Call create_tracker on the AI Config for a new run. #{flag_data}")
166
+ return
167
+ end
104
168
  @summary.time_to_first_token = time_to_first_token
105
169
  @ld_client.track(
106
170
  '$ld:ai:tokens:ttf',
@@ -111,11 +175,17 @@ module LaunchDarkly
111
175
  end
112
176
 
113
177
  #
114
- # Track user feedback
178
+ # Track user feedback.
179
+ #
180
+ # Records at most once per Tracker; further calls are ignored.
115
181
  #
116
182
  # @param kind [Symbol] The kind of feedback (:positive or :negative)
117
183
  #
118
184
  def track_feedback(kind:)
185
+ unless @summary.feedback.nil?
186
+ @logger&.warn("Skipping track_feedback: feedback already recorded on this tracker. Call create_tracker on the AI Config for a new run. #{flag_data}")
187
+ return
188
+ end
119
189
  @summary.feedback = kind
120
190
  event_name = kind == :positive ? '$ld:ai:feedback:user:positive' : '$ld:ai:feedback:user:negative'
121
191
  @ld_client.track(
@@ -127,9 +197,17 @@ module LaunchDarkly
127
197
  end
128
198
 
129
199
  #
130
- # Track a successful AI generation
200
+ # Track a successful AI generation.
201
+ #
202
+ # Records at most once per Tracker. track_success and track_error share
203
+ # state; only one of the two can record per Tracker, and subsequent
204
+ # calls are ignored.
131
205
  #
132
206
  def track_success
207
+ unless @summary.success.nil?
208
+ @logger&.warn("Skipping track_success: success/error already recorded on this tracker. Call create_tracker on the AI Config for a new run. #{flag_data}")
209
+ return
210
+ end
133
211
  @summary.success = true
134
212
  @ld_client.track(
135
213
  '$ld:ai:generation:success',
@@ -140,9 +218,17 @@ module LaunchDarkly
140
218
  end
141
219
 
142
220
  #
143
- # Track an error in AI generation
221
+ # Track an error in AI generation.
222
+ #
223
+ # Records at most once per Tracker. track_success and track_error share
224
+ # state; only one of the two can record per Tracker, and subsequent
225
+ # calls are ignored.
144
226
  #
145
227
  def track_error
228
+ unless @summary.success.nil?
229
+ @logger&.warn("Skipping track_error: success/error already recorded on this tracker. Call create_tracker on the AI Config for a new run. #{flag_data}")
230
+ return
231
+ end
146
232
  @summary.success = false
147
233
  @ld_client.track(
148
234
  '$ld:ai:generation:error',
@@ -153,11 +239,17 @@ module LaunchDarkly
153
239
  end
154
240
 
155
241
  #
156
- # Track token usage
242
+ # Track token usage.
243
+ #
244
+ # Records at most once per Tracker; further calls are ignored.
157
245
  #
158
246
  # @param token_usage [TokenUsage] An object containing token usage details
159
247
  #
160
248
  def track_tokens(token_usage)
249
+ unless @summary.usage.nil?
250
+ @logger&.warn("Skipping track_tokens: token usage already recorded on this tracker. Call create_tracker on the AI Config for a new run. #{flag_data}")
251
+ return
252
+ end
161
253
  @summary.usage = token_usage
162
254
  if token_usage.total.positive?
163
255
  @ld_client.track(
@@ -191,6 +283,10 @@ module LaunchDarkly
191
283
  # If the provided block raises, this method will also raise.
192
284
  # A failed operation will not have any token usage data.
193
285
  #
286
+ # Subsequent calls re-run the inner block but emit only metrics not
287
+ # already recorded on this Tracker. Call create_tracker on the AI
288
+ # Config to start a new run.
289
+ #
194
290
  # @yield The block to track.
195
291
  # @return The result of the tracked block.
196
292
  #
@@ -208,6 +304,10 @@ module LaunchDarkly
208
304
  # Track AWS Bedrock conversation operations.
209
305
  # This method tracks the duration, token usage, and success/error status.
210
306
  #
307
+ # Subsequent calls re-run the inner block but emit only metrics not
308
+ # already recorded on this Tracker. Call create_tracker on the AI
309
+ # Config to start a new run.
310
+ #
211
311
  # @yield The block to track.
212
312
  # @return [Hash] The original response hash.
213
313
  #
@@ -222,13 +322,15 @@ module LaunchDarkly
222
322
  end
223
323
 
224
324
  private def flag_data
225
- {
226
- variationKey: @variation_key,
325
+ data = {
326
+ runId: @run_id,
227
327
  configKey: @config_key,
228
328
  version: @version,
229
329
  modelName: @model_name,
230
330
  providerName: @provider_name,
231
331
  }
332
+ data[:variationKey] = @variation_key if @variation_key && !@variation_key.empty?
333
+ data
232
334
  end
233
335
 
234
336
  private def openai_to_token_usage(usage)
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'ldclient-rb'
4
4
  require 'mustache'
5
+ require 'securerandom'
5
6
  require_relative 'ai_config_tracker'
6
7
  require_relative 'sdk_info'
7
8
 
@@ -100,26 +101,65 @@ module LaunchDarkly
100
101
  end
101
102
 
102
103
  #
103
- # The AIConfig class represents an AI configuration.
104
+ # The AIConfigDefault class represents a user-provided fallback AI
105
+ # configuration.
104
106
  #
105
- class AIConfig
106
- attr_reader :enabled, :messages, :tracker, :model, :provider
107
+ # Pass an instance of this class as the +default:+ parameter to
108
+ # {Client#completion_config} to control the fallback values when a flag
109
+ # is not found or cannot be evaluated.
110
+ #
111
+ # This is an input-only type: it is what an application supplies to the
112
+ # SDK, never what the SDK returns. The SDK always returns an
113
+ # {AIConfig}, which carries a tracker factory; AIConfigDefault does not.
114
+ #
115
+ class AIConfigDefault
116
+ attr_reader :enabled, :messages, :model, :provider
107
117
 
108
- def initialize(enabled: nil, model: nil, messages: nil, tracker: nil, provider: nil)
118
+ #
119
+ # Returns a new AIConfigDefault with enabled: false and no model,
120
+ # messages, or provider.
121
+ #
122
+ # @return [AIConfigDefault] a new disabled fallback config
123
+ #
124
+ def self.disabled
125
+ new(enabled: false)
126
+ end
127
+
128
+ def initialize(enabled: false, model: nil, messages: nil, provider: nil)
109
129
  @enabled = enabled
110
130
  @messages = messages
111
- @tracker = tracker
112
131
  @model = model
113
132
  @provider = provider
114
133
  end
115
134
 
116
- #
117
- # Returns a new disabled AIConfig instance.
118
- #
119
- # @return [AIConfig] a new disabled config
120
- #
121
- def self.disabled
122
- new(enabled: false)
135
+ def to_h
136
+ {
137
+ _ldMeta: {
138
+ enabled: @enabled || false,
139
+ },
140
+ messages: @messages.is_a?(Array) ? @messages.map { |msg| msg&.to_h } : nil,
141
+ model: @model&.to_h,
142
+ provider: @provider&.to_h,
143
+ }
144
+ end
145
+ end
146
+
147
+ #
148
+ # The AIConfig class represents an AI configuration returned by the SDK.
149
+ #
150
+ # Instances are created by {Client#completion_config} and always carry a
151
+ # tracker factory; see {#create_tracker}. Do not instantiate directly.
152
+ # For application-supplied fallback values, use {AIConfigDefault}.
153
+ #
154
+ class AIConfig
155
+ attr_reader :enabled, :messages, :model, :provider
156
+
157
+ def initialize(tracker_factory:, enabled: nil, model: nil, messages: nil, provider: nil)
158
+ @enabled = enabled
159
+ @messages = messages
160
+ @model = model
161
+ @provider = provider
162
+ @tracker_factory = tracker_factory
123
163
  end
124
164
 
125
165
  def to_h
@@ -132,6 +172,18 @@ module LaunchDarkly
132
172
  provider: @provider&.to_h,
133
173
  }
134
174
  end
175
+
176
+ #
177
+ # Creates a new tracker for a fresh AI run. Each call mints a new
178
+ # runId (a UUIDv4) that LaunchDarkly uses to correlate the tracker's
179
+ # events in metrics views. Call this once per AI run; metrics from
180
+ # different runIds cannot be combined.
181
+ #
182
+ # @return [AIConfigTracker] a new tracker instance
183
+ #
184
+ def create_tracker
185
+ @tracker_factory.call
186
+ end
135
187
  end
136
188
 
137
189
  #
@@ -172,14 +224,26 @@ module LaunchDarkly
172
224
  #
173
225
  # @param key [String] The key of the configuration flag
174
226
  # @param context [LDContext] The context used when evaluating the flag
175
- # @param default [AIConfig] The default value to use if the flag is not found
227
+ # @param default [AIConfigDefault] The default value to use if the flag is not found
176
228
  # @param variables [Hash] Optional variables for rendering messages
177
229
  # @return [AIConfig] An AIConfig instance containing the configuration data
178
230
  #
179
231
  def completion_config(key:, context:, default: nil, variables: nil)
180
232
  @ld_client.track(TRACK_USAGE_COMPLETION_CONFIG, context, key, 1)
181
233
 
182
- _completion_config(key:, context:, default: default || AIConfig.disabled, variables:)
234
+ _completion_config(key:, context:, default: default || AIConfigDefault.disabled, variables:)
235
+ end
236
+
237
+ #
238
+ # Reconstructs a tracker from a resumption token, allowing deferred tracking
239
+ # (e.g. feedback from a different process).
240
+ #
241
+ # @param token [String] A resumption token obtained from AIConfigTracker#resumption_token
242
+ # @param context [LDContext] The context for track events
243
+ # @return [AIConfigTracker] A new tracker instance
244
+ #
245
+ def create_tracker(token:, context:)
246
+ AIConfigTracker.from_resumption_token(token: token, ld_client: @ld_client, context: context)
183
247
  end
184
248
 
185
249
  # @deprecated Use {#completion_config} instead.
@@ -226,20 +290,28 @@ module LaunchDarkly
226
290
  )
227
291
  end
228
292
 
229
- tracker = LaunchDarkly::Server::AI::AIConfigTracker.new(
230
- ld_client: @ld_client,
231
- variation_key: variation.dig(:_ldMeta, :variationKey) || '',
232
- config_key: key,
233
- version: variation.dig(:_ldMeta, :version) || 1,
234
- model_name: model&.name || '',
235
- provider_name: provider_config&.name || '',
236
- context:
237
- )
293
+ variation_key = variation.dig(:_ldMeta, :variationKey) || ''
294
+ version = variation.dig(:_ldMeta, :version) || 1
295
+ model_name = model&.name || ''
296
+ provider_name = provider_config&.name || ''
297
+
298
+ tracker_factory = lambda {
299
+ LaunchDarkly::Server::AI::AIConfigTracker.new(
300
+ ld_client: @ld_client,
301
+ run_id: SecureRandom.uuid,
302
+ variation_key: variation_key,
303
+ config_key: key,
304
+ version: version,
305
+ model_name: model_name,
306
+ provider_name: provider_name,
307
+ context: context
308
+ )
309
+ }
238
310
 
239
311
  AIConfig.new(
240
312
  enabled: variation.dig(:_ldMeta, :enabled) || false,
241
313
  messages:,
242
- tracker:,
314
+ tracker_factory:,
243
315
  model:,
244
316
  provider: provider_config
245
317
  )
@@ -3,7 +3,7 @@
3
3
  module LaunchDarkly
4
4
  module Server
5
5
  module AI
6
- VERSION = '0.3.0' # x-release-please-version
6
+ VERSION = '0.4.0' # x-release-please-version
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: launchdarkly-server-sdk-ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - LaunchDarkly
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-05 00:00:00.000000000 Z
11
+ date: 2026-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: base64
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: launchdarkly-server-sdk
15
29
  requirement: !ruby/object:Gem::Requirement