launchdarkly-server-sdk-ai 0.2.2 → 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: 3ed5a6f605ce2c7970cbe5045fbd0b8700ab0cd6c62fe56a78ee49f155042dde
4
- data.tar.gz: 91ae21b4e9b95bb3d0b87e266c420c7568ea91ad40a77d654eedfa900adc2099
3
+ metadata.gz: e1bdaa1e6cd790510ce57f6e0d63b3719f571ec7b5299cecc430995158240897
4
+ data.tar.gz: bca165be2cc34d56f742d1d36ed89f199ba9a48c6d9e9f9fea88d1397d5af89f
5
5
  SHA512:
6
- metadata.gz: 176ba55618c6885959240a1900400e14d3d55a2896f7c768319b169ed6c8ff2f762fa2f066d913bac5c3e502775e089d81a4e92a583ace6f714fcb73fc607b4b
7
- data.tar.gz: a49cde18e9d3d599a2b58a6174b9c3bb0eba1d320b766681b60d6e34651b8b8c86252be1f840a9fb8ad31b62ae4a3b1ccfd2ac953f4ca241f3bc4d3c69b97fea
6
+ metadata.gz: 3586d99728f38b474dce7a35d9721a1871610f65eb1336070cce5bad4dbff557181c4956fb25e4b6d5230e18fe0da5c43281164bdb387de4a98d3d4b3d42f09c
7
+ data.tar.gz: 1f80b447eb3f615380b353ec87b88fb2c997e332a6515adf60bb9322d3d76a625e4a4f061ca536c1a19ecca052d67e90a433df67cd717ad3836d14c5a7dfd8fe
data/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
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
+
20
+ ## [0.3.0](https://github.com/launchdarkly/ruby-server-sdk-ai/compare/0.2.2...0.3.0) (2026-03-05)
21
+
22
+
23
+ ### ⚠ BREAKING CHANGES
24
+
25
+ * Use kwargs for completion_config and config methods
26
+ * Return disabled config if no defaultValue is provided ([#23](https://github.com/launchdarkly/ruby-server-sdk-ai/issues/23))
27
+
28
+ ### Features
29
+
30
+ * Drop support for Ruby 3.0 which is EOL. ([fe3fdf8](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/fe3fdf8c022dd4e53e43e9311d76e3b5a098af75))
31
+ * Use kwargs for completion_config and config methods ([fe3fdf8](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/fe3fdf8c022dd4e53e43e9311d76e3b5a098af75))
32
+
33
+
34
+ ### Bug Fixes
35
+
36
+ * Return disabled config if no defaultValue is provided ([#23](https://github.com/launchdarkly/ruby-server-sdk-ai/issues/23)) ([fe3fdf8](https://github.com/launchdarkly/ruby-server-sdk-ai/commit/fe3fdf8c022dd4e53e43e9311d76e3b5a098af75))
37
+
3
38
  ## [0.2.2](https://github.com/launchdarkly/ruby-server-sdk-ai/compare/0.2.1...0.2.2) (2026-02-25)
4
39
 
5
40
 
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.2.2
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.2.2
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/README.md CHANGED
@@ -27,7 +27,7 @@ LaunchDarkly overview
27
27
  Supported Ruby versions
28
28
  -----------------------
29
29
 
30
- This version of the library has a minimum Ruby version of 3.0.0, or 9.4.0 for JRuby.
30
+ This version of the library has a minimum Ruby version of 3.1.0, or 9.4.0 for JRuby.
31
31
 
32
32
  Getting started
33
33
  -----------
@@ -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,17 +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.
106
+ #
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
117
+
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)
129
+ @enabled = enabled
130
+ @messages = messages
131
+ @model = model
132
+ @provider = provider
133
+ end
134
+
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}.
104
153
  #
105
154
  class AIConfig
106
- attr_reader :enabled, :messages, :tracker, :model, :provider
155
+ attr_reader :enabled, :messages, :model, :provider
107
156
 
108
- def initialize(enabled: nil, model: nil, messages: nil, tracker: nil, provider: nil)
157
+ def initialize(tracker_factory:, enabled: nil, model: nil, messages: nil, provider: nil)
109
158
  @enabled = enabled
110
159
  @messages = messages
111
- @tracker = tracker
112
160
  @model = model
113
161
  @provider = provider
162
+ @tracker_factory = tracker_factory
114
163
  end
115
164
 
116
165
  def to_h
@@ -123,6 +172,18 @@ module LaunchDarkly
123
172
  provider: @provider&.to_h,
124
173
  }
125
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
126
187
  end
127
188
 
128
189
  #
@@ -161,31 +222,43 @@ module LaunchDarkly
161
222
  #
162
223
  # Retrieves the AIConfig
163
224
  #
164
- # @param config_key [String] The key of the configuration flag
225
+ # @param key [String] The key of the configuration flag
165
226
  # @param context [LDContext] The context used when evaluating the flag
166
- # @param default_value [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
167
228
  # @param variables [Hash] Optional variables for rendering messages
168
229
  # @return [AIConfig] An AIConfig instance containing the configuration data
169
230
  #
170
- def completion_config(config_key, context, default_value = nil, variables = nil)
171
- @ld_client.track(TRACK_USAGE_COMPLETION_CONFIG, context, config_key, 1)
231
+ def completion_config(key:, context:, default: nil, variables: nil)
232
+ @ld_client.track(TRACK_USAGE_COMPLETION_CONFIG, context, key, 1)
172
233
 
173
- _completion_config(config_key, context, default_value, 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)
174
247
  end
175
248
 
176
249
  # @deprecated Use {#completion_config} instead.
177
- def config(config_key, context, default_value = nil, variables = nil)
250
+ def config(key:, context:, default: nil, variables: nil)
178
251
  warn '[DEPRECATION] `config` is deprecated. Use `completion_config` instead.'
179
- completion_config(config_key, context, default_value, variables)
252
+ completion_config(key:, context:, default:, variables:)
180
253
  end
181
254
 
182
255
  private
183
256
 
184
- def _completion_config(config_key, context, default_value = nil, variables = nil)
257
+ def _completion_config(key:, context:, default:, variables: nil)
185
258
  variation = @ld_client.variation(
186
- config_key,
259
+ key,
187
260
  context,
188
- default_value.respond_to?(:to_h) ? default_value.to_h : nil
261
+ default.respond_to?(:to_h) ? default.to_h : nil
189
262
  )
190
263
 
191
264
  all_variables = variables ? variables.dup : {}
@@ -217,21 +290,29 @@ module LaunchDarkly
217
290
  )
218
291
  end
219
292
 
220
- tracker = LaunchDarkly::Server::AI::AIConfigTracker.new(
221
- ld_client: @ld_client,
222
- variation_key: variation.dig(:_ldMeta, :variationKey) || '',
223
- config_key: config_key,
224
- version: variation.dig(:_ldMeta, :version) || 1,
225
- model_name: model&.name || '',
226
- provider_name: provider_config&.name || '',
227
- context: context
228
- )
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
+ }
229
310
 
230
311
  AIConfig.new(
231
312
  enabled: variation.dig(:_ldMeta, :enabled) || false,
232
- messages: messages,
233
- tracker: tracker,
234
- model: model,
313
+ messages:,
314
+ tracker_factory:,
315
+ model:,
235
316
  provider: provider_config
236
317
  )
237
318
  end
@@ -3,7 +3,7 @@
3
3
  module LaunchDarkly
4
4
  module Server
5
5
  module AI
6
- VERSION = '0.2.2' # 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,14 +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.2.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - LaunchDarkly
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-05-15 00:00:00.000000000 Z
11
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'
12
27
  - !ruby/object:Gem::Dependency
13
28
  name: launchdarkly-server-sdk
14
29
  requirement: !ruby/object:Gem::Requirement
@@ -162,6 +177,7 @@ licenses:
162
177
  metadata:
163
178
  source_code_uri: https://github.com/launchdarkly/ruby-server-sdk-ai
164
179
  changelog_uri: https://github.com/launchdarkly/ruby-server-sdk-ai/blob/main/CHANGELOG.md
180
+ post_install_message:
165
181
  rdoc_options: []
166
182
  require_paths:
167
183
  - lib
@@ -169,14 +185,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
169
185
  requirements:
170
186
  - - ">="
171
187
  - !ruby/object:Gem::Version
172
- version: 3.0.0
188
+ version: 3.1.0
173
189
  required_rubygems_version: !ruby/object:Gem::Requirement
174
190
  requirements:
175
191
  - - ">="
176
192
  - !ruby/object:Gem::Version
177
193
  version: '0'
178
194
  requirements: []
179
- rubygems_version: 3.6.9
195
+ rubygems_version: 3.3.27
196
+ signing_key:
180
197
  specification_version: 4
181
198
  summary: LaunchDarkly AI SDK for Ruby
182
199
  test_files: []