launchdarkly-server-sdk-ai 0.3.0 → 0.5.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: b23fa19941fcf0773e891e61be94c5761a0889cf2c7a5b362f9b4e0e5986b6ba
4
+ data.tar.gz: 4ca5652c32a43e51b39f0a00bca215bd2bc4d1db1340da04441b33d48ccdc758
5
5
  SHA512:
6
- metadata.gz: 1fb7745e91ce1fb85b965c72f033d3c6ea8978f47d30aa691502251bba83238fbe279d5b67cc3a4e00b8669f3509bf61591bba7fa865f3147db2afc9cb5d3361
7
- data.tar.gz: dde93cdaac0a5cf24e6c684d9b1769f9c5e6092e1140bf6f8125e6bc8a2eb0c485cc67f460b195366473f8ca2cda6ba41618d57e2c8e08e73798613c590901fa
6
+ metadata.gz: a51ee90183e5421ea039d3340ca107b36100e24bfcaf8a496ded3a461424fb0e5a8adffa46ba802519db976d290f50443386aa6c8c317bb4ebac1d61675f608e
7
+ data.tar.gz: 84cc8dd823cd58a6880fa819ed5f1485cdfc1324422cd4124905175400287dbe351319e4581b30c821410ab74e1a6b58e4d553cb5b0e5068a39d56dd0d31348d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.0](https://github.com/launchdarkly/ruby-server-sdk-ai/compare/0.4.0...0.5.0) (2026-09-02)
4
+
5
+
6
+ ### Features
7
+
8
+ * **server-ai:** stamp modelKey and modelVersion on AI usage events (AIC-2857) ([#36](https://github.com/launchdarkly/ruby-server-sdk-ai/issues/36)) ([2f7fda4](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/2f7fda4d57b09483cf15fda6e3102e24b1eecb27))
9
+
10
+
11
+ ## [0.4.0](https://github.com/launchdarkly/ruby-server-sdk-ai/compare/0.3.0...0.4.0) (2026-05-15)
12
+
13
+
14
+ ### ⚠ BREAKING CHANGES
15
+
16
+ * Replace tracker tuple from completion_config with AIConfig#create_tracker factory
17
+ * Track each AIConfigTracker metric at most once per tracker
18
+ * Add per-execution runId, at-most-once tracking, and cross-process tracker resumption
19
+
20
+ ### Features
21
+
22
+ * Add Client#create_tracker(token:, context:) to resume a tracker across processes ([20f06f1](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/20f06f1f24a42692953ae44cebea70aaa416b29d))
23
+ * Add per-execution runId to correlate AIConfigTracker events ([20f06f1](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/20f06f1f24a42692953ae44cebea70aaa416b29d))
24
+ * Add per-execution runId, at-most-once tracking, and cross-process tracker resumption ([20f06f1](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/20f06f1f24a42692953ae44cebea70aaa416b29d))
25
+ * Replace tracker tuple from completion_config with AIConfig#create_tracker factory ([20f06f1](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/20f06f1f24a42692953ae44cebea70aaa416b29d))
26
+ * Track each AIConfigTracker metric at most once per tracker ([20f06f1](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/20f06f1f24a42692953ae44cebea70aaa416b29d))
27
+
3
28
  ## [0.3.0](https://github.com/launchdarkly/ruby-server-sdk-ai/compare/0.2.2...0.3.0) (2026-03-05)
4
29
 
5
30
 
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.5.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.
data/SECURITY.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Reporting and Fixing Security Issues
2
2
 
3
- Please report all security issues to the LaunchDarkly security team by submitting a bug bounty report to our [HackerOne program](https://hackerone.com/launchdarkly?type=team). LaunchDarkly will triage and address all valid security issues following the response targets defined in our program policy. Valid security issues may be eligible for a bounty.
3
+ **Do not open Issues or Pull Requests for security issues.**
4
+ This will make potential issues publicly visible before LaunchDarkly's Security Team can address them, which could lead to a compromise of the platform and negatively impact our customers.
4
5
 
5
- Please do not open issues or pull requests for security issues. This makes the problem immediately visible to everyone, including potentially malicious actors.
6
+ Security issues must be reported through our [Bug Bounty program](https://bugcrowd.com/engagements/launchdarkly-mbb-og), following the program policy, for triage and remediation by the LaunchDarkly Security Team. Valid security issues may be eligible for a bounty.
7
+
8
+ Please do not attempt to directly contact members of LaunchDarkly staff.
@@ -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,10 +41,18 @@ 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
- attr_reader :ld_client, :config_key, :context, :variation_key, :version, :summary, :model_name, :provider_name
54
+ attr_reader :ld_client, :config_key, :context, :variation_key, :version, :summary, :model_name, :provider_name,
55
+ :model_key, :model_version
46
56
 
47
57
  #
48
58
  # Initialize a new AIConfigTracker instance.
@@ -53,25 +63,78 @@ module LaunchDarkly
53
63
  # @param version [Integer] The version number
54
64
  # @param model_name [String] The name of the AI model being used
55
65
  # @param provider_name [String] The name of the AI provider
66
+ # @param model_key [String, nil] The stable, unique key of the model used
67
+ # @param model_version [Integer] The pinned version of the model used
56
68
  # @param context [LDContext] The context used for the flag evaluation
57
69
  #
58
- def initialize(ld_client:, variation_key:, config_key:, version:, model_name:, provider_name:, context:)
70
+ def initialize(ld_client:, run_id:, config_key:, variation_key:, version:, context:, model_name:, provider_name:,
71
+ model_key: nil, model_version: 1)
59
72
  @ld_client = ld_client
60
73
  @variation_key = variation_key
61
74
  @config_key = config_key
62
75
  @version = version
63
76
  @model_name = model_name
64
77
  @provider_name = provider_name
78
+ @model_key = model_key
79
+ @model_version = model_version
65
80
  @context = context
66
81
  @summary = MetricSummary.new
82
+ @run_id = run_id
83
+ @logger = LaunchDarkly::Server::AI.default_logger
84
+ end
85
+
86
+ #
87
+ # Returns a URL-safe Base64-encoded JSON token that can be used to reconstruct
88
+ # a tracker in a different process (e.g. for deferred feedback).
89
+ #
90
+ # The token contains: runId, configKey, variationKey, version.
91
+ # modelName, providerName, modelKey, and modelVersion are NOT included.
92
+ #
93
+ # @return [String] the resumption token
94
+ #
95
+ def resumption_token
96
+ payload = { runId: @run_id, configKey: @config_key }
97
+ payload[:variationKey] = @variation_key if @variation_key && !@variation_key.empty?
98
+ payload[:version] = @version
99
+ Base64.urlsafe_encode64(JSON.generate(payload), padding: false)
100
+ end
101
+
102
+ #
103
+ # Reconstructs a tracker from a resumption token.
104
+ #
105
+ # @param token [String] A URL-safe Base64-encoded JSON resumption token
106
+ # @param ld_client [LDClient] The LaunchDarkly client instance
107
+ # @param context [LDContext] The context for track events
108
+ # @return [AIConfigTracker] A new tracker instance
109
+ #
110
+ def self.from_resumption_token(token:, ld_client:, context:)
111
+ json = Base64.urlsafe_decode64(token)
112
+ payload = JSON.parse(json)
113
+
114
+ new(
115
+ ld_client: ld_client,
116
+ run_id: payload['runId'],
117
+ config_key: payload['configKey'],
118
+ variation_key: payload.fetch('variationKey', ''),
119
+ version: payload['version'],
120
+ context: context,
121
+ model_name: '',
122
+ provider_name: ''
123
+ )
67
124
  end
68
125
 
69
126
  #
70
- # Track the duration of an AI operation
127
+ # Track the duration of an AI run.
128
+ #
129
+ # Records at most once per Tracker; further calls are ignored.
71
130
  #
72
131
  # @param duration [Integer] The duration in milliseconds
73
132
  #
74
133
  def track_duration(duration)
134
+ unless @summary.duration.nil?
135
+ @logger&.warn("Skipping track_duration: duration already recorded on this tracker. Call create_tracker on the AI Config for a new run. #{flag_data}")
136
+ return
137
+ end
75
138
  @summary.duration = duration
76
139
  @ld_client.track(
77
140
  '$ld:ai:duration:total',
@@ -96,11 +159,18 @@ module LaunchDarkly
96
159
  end
97
160
 
98
161
  #
99
- # Track time to first token
162
+ # Track time to first token.
163
+ #
164
+ # Records at most once per Tracker; further calls are ignored.
100
165
  #
101
166
  # @param duration [Integer] The duration in milliseconds
102
167
  #
103
168
  def track_time_to_first_token(time_to_first_token)
169
+ unless @summary.time_to_first_token.nil?
170
+ @logger&.warn("Skipping track_time_to_first_token: time-to-first-token already recorded on this tracker. " \
171
+ "Call create_tracker on the AI Config for a new run. #{flag_data}")
172
+ return
173
+ end
104
174
  @summary.time_to_first_token = time_to_first_token
105
175
  @ld_client.track(
106
176
  '$ld:ai:tokens:ttf',
@@ -111,11 +181,17 @@ module LaunchDarkly
111
181
  end
112
182
 
113
183
  #
114
- # Track user feedback
184
+ # Track user feedback.
185
+ #
186
+ # Records at most once per Tracker; further calls are ignored.
115
187
  #
116
188
  # @param kind [Symbol] The kind of feedback (:positive or :negative)
117
189
  #
118
190
  def track_feedback(kind:)
191
+ unless @summary.feedback.nil?
192
+ @logger&.warn("Skipping track_feedback: feedback already recorded on this tracker. Call create_tracker on the AI Config for a new run. #{flag_data}")
193
+ return
194
+ end
119
195
  @summary.feedback = kind
120
196
  event_name = kind == :positive ? '$ld:ai:feedback:user:positive' : '$ld:ai:feedback:user:negative'
121
197
  @ld_client.track(
@@ -127,9 +203,17 @@ module LaunchDarkly
127
203
  end
128
204
 
129
205
  #
130
- # Track a successful AI generation
206
+ # Track a successful AI generation.
207
+ #
208
+ # Records at most once per Tracker. track_success and track_error share
209
+ # state; only one of the two can record per Tracker, and subsequent
210
+ # calls are ignored.
131
211
  #
132
212
  def track_success
213
+ unless @summary.success.nil?
214
+ @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}")
215
+ return
216
+ end
133
217
  @summary.success = true
134
218
  @ld_client.track(
135
219
  '$ld:ai:generation:success',
@@ -140,9 +224,17 @@ module LaunchDarkly
140
224
  end
141
225
 
142
226
  #
143
- # Track an error in AI generation
227
+ # Track an error in AI generation.
228
+ #
229
+ # Records at most once per Tracker. track_success and track_error share
230
+ # state; only one of the two can record per Tracker, and subsequent
231
+ # calls are ignored.
144
232
  #
145
233
  def track_error
234
+ unless @summary.success.nil?
235
+ @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}")
236
+ return
237
+ end
146
238
  @summary.success = false
147
239
  @ld_client.track(
148
240
  '$ld:ai:generation:error',
@@ -153,11 +245,17 @@ module LaunchDarkly
153
245
  end
154
246
 
155
247
  #
156
- # Track token usage
248
+ # Track token usage.
249
+ #
250
+ # Records at most once per Tracker; further calls are ignored.
157
251
  #
158
252
  # @param token_usage [TokenUsage] An object containing token usage details
159
253
  #
160
254
  def track_tokens(token_usage)
255
+ unless @summary.usage.nil?
256
+ @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}")
257
+ return
258
+ end
161
259
  @summary.usage = token_usage
162
260
  if token_usage.total.positive?
163
261
  @ld_client.track(
@@ -191,6 +289,10 @@ module LaunchDarkly
191
289
  # If the provided block raises, this method will also raise.
192
290
  # A failed operation will not have any token usage data.
193
291
  #
292
+ # Subsequent calls re-run the inner block but emit only metrics not
293
+ # already recorded on this Tracker. Call create_tracker on the AI
294
+ # Config to start a new run.
295
+ #
194
296
  # @yield The block to track.
195
297
  # @return The result of the tracked block.
196
298
  #
@@ -208,6 +310,10 @@ module LaunchDarkly
208
310
  # Track AWS Bedrock conversation operations.
209
311
  # This method tracks the duration, token usage, and success/error status.
210
312
  #
313
+ # Subsequent calls re-run the inner block but emit only metrics not
314
+ # already recorded on this Tracker. Call create_tracker on the AI
315
+ # Config to start a new run.
316
+ #
211
317
  # @yield The block to track.
212
318
  # @return [Hash] The original response hash.
213
319
  #
@@ -222,13 +328,17 @@ module LaunchDarkly
222
328
  end
223
329
 
224
330
  private def flag_data
225
- {
226
- variationKey: @variation_key,
331
+ data = {
332
+ runId: @run_id,
227
333
  configKey: @config_key,
228
334
  version: @version,
229
335
  modelName: @model_name,
230
336
  providerName: @provider_name,
337
+ modelVersion: @model_version,
231
338
  }
339
+ data[:variationKey] = @variation_key if @variation_key && !@variation_key.empty?
340
+ data[:modelKey] = @model_key if @model_key && !@model_key.empty?
341
+ data
232
342
  end
233
343
 
234
344
  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,32 @@ 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
+ model_key = variation.dig(:_ldMeta, :modelKey)
298
+ model_version = (variation.dig(:_ldMeta, :modelVersion) || 1).to_i
299
+
300
+ tracker_factory = lambda {
301
+ LaunchDarkly::Server::AI::AIConfigTracker.new(
302
+ ld_client: @ld_client,
303
+ run_id: SecureRandom.uuid,
304
+ variation_key: variation_key,
305
+ config_key: key,
306
+ version: version,
307
+ model_name: model_name,
308
+ provider_name: provider_name,
309
+ model_key: model_key,
310
+ model_version: model_version,
311
+ context: context
312
+ )
313
+ }
238
314
 
239
315
  AIConfig.new(
240
316
  enabled: variation.dig(:_ldMeta, :enabled) || false,
241
317
  messages:,
242
- tracker:,
318
+ tracker_factory:,
243
319
  model:,
244
320
  provider: provider_config
245
321
  )
@@ -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.5.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.5.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-09-02 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