@launchdarkly/server-sdk-ai 0.10.1 → 0.11.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/__tests__/LDAIClientImpl.test.ts +328 -1
  3. package/__tests__/LDAIConfigTrackerImpl.test.ts +94 -136
  4. package/dist/LDAIClientImpl.d.ts +7 -0
  5. package/dist/LDAIClientImpl.d.ts.map +1 -1
  6. package/dist/LDAIClientImpl.js +60 -11
  7. package/dist/LDAIClientImpl.js.map +1 -1
  8. package/dist/LDAIConfigTrackerImpl.d.ts +3 -1
  9. package/dist/LDAIConfigTrackerImpl.d.ts.map +1 -1
  10. package/dist/LDAIConfigTrackerImpl.js +5 -3
  11. package/dist/LDAIConfigTrackerImpl.js.map +1 -1
  12. package/dist/api/LDAIClient.d.ts +70 -0
  13. package/dist/api/LDAIClient.d.ts.map +1 -1
  14. package/dist/api/agents/LDAIAgent.d.ts +32 -0
  15. package/dist/api/agents/LDAIAgent.d.ts.map +1 -0
  16. package/dist/api/agents/LDAIAgent.js +3 -0
  17. package/dist/api/agents/LDAIAgent.js.map +1 -0
  18. package/dist/api/agents/index.d.ts +2 -0
  19. package/dist/api/agents/index.d.ts.map +1 -0
  20. package/dist/api/agents/index.js +18 -0
  21. package/dist/api/agents/index.js.map +1 -0
  22. package/dist/api/index.d.ts +1 -0
  23. package/dist/api/index.d.ts.map +1 -1
  24. package/dist/api/index.js +1 -0
  25. package/dist/api/index.js.map +1 -1
  26. package/docs/assets/highlight.css +15 -8
  27. package/docs/assets/search.js +1 -1
  28. package/docs/enums/LDFeedbackKind.html +11 -8
  29. package/docs/functions/createBedrockTokenUsage.html +9 -6
  30. package/docs/functions/createOpenAiUsage.html +9 -6
  31. package/docs/functions/createVercelAISDKTokenUsage.html +9 -6
  32. package/docs/functions/initAi.html +9 -6
  33. package/docs/index.html +12 -6
  34. package/docs/interfaces/LDAIAgent.html +177 -0
  35. package/docs/interfaces/LDAIAgentConfig.html +111 -0
  36. package/docs/interfaces/LDAIClient.html +96 -10
  37. package/docs/interfaces/LDAIConfig.html +15 -12
  38. package/docs/interfaces/LDAIConfigTracker.html +21 -18
  39. package/docs/interfaces/LDMessage.html +11 -8
  40. package/docs/interfaces/LDModelConfig.html +12 -9
  41. package/docs/interfaces/LDProviderConfig.html +10 -7
  42. package/docs/interfaces/LDTokenUsage.html +12 -9
  43. package/docs/interfaces/VercelAISDKConfig.html +19 -16
  44. package/docs/interfaces/VercelAISDKMapOptions.html +10 -7
  45. package/docs/types/LDAIAgentDefaults.html +63 -0
  46. package/docs/types/LDAIDefaults.html +9 -6
  47. package/docs/types/VercelAISDKProvider.html +9 -6
  48. package/package.json +1 -1
  49. package/src/LDAIClientImpl.ts +146 -12
  50. package/src/LDAIConfigTrackerImpl.ts +11 -3
  51. package/src/api/LDAIClient.ts +80 -0
  52. package/src/api/agents/LDAIAgent.ts +36 -0
  53. package/src/api/agents/index.ts +1 -0
  54. package/src/api/index.ts +1 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.11.0](https://github.com/launchdarkly/js-core/compare/server-sdk-ai-v0.10.1...server-sdk-ai-v0.11.0) (2025-08-01)
4
+
5
+
6
+ ### Features
7
+
8
+ * Adding agent support for AI Configs ([#893](https://github.com/launchdarkly/js-core/issues/893)) ([bf95b92](https://github.com/launchdarkly/js-core/commit/bf95b92946e93b54e1eda7ffef96039b2b42b9aa))
9
+ * Update AI tracker to include model & provider name for metrics generation ([#901](https://github.com/launchdarkly/js-core/issues/901)) ([9474862](https://github.com/launchdarkly/js-core/commit/94748621034ed6b1a74060ee0c536bf96a3cd43d))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * Remove deprecated track generation event ([#902](https://github.com/launchdarkly/js-core/issues/902)) ([40f8593](https://github.com/launchdarkly/js-core/commit/40f859386087a443948214e9b535527f125ffa39))
15
+
3
16
  ## [0.10.1](https://github.com/launchdarkly/js-core/compare/server-sdk-ai-v0.10.0...server-sdk-ai-v0.10.1) (2025-07-23)
4
17
 
5
18
 
@@ -1,6 +1,12 @@
1
1
  import { LDContext } from '@launchdarkly/js-server-sdk-common';
2
2
 
3
- import { LDAIDefaults } from '../src/api/config';
3
+ import { LDAIAgentDefaults } from '../src/api/agents';
4
+ import {
5
+ LDAIDefaults,
6
+ VercelAISDKConfig,
7
+ VercelAISDKMapOptions,
8
+ VercelAISDKProvider,
9
+ } from '../src/api/config';
4
10
  import { LDAIClientImpl } from '../src/LDAIClientImpl';
5
11
  import { LDClientMin } from '../src/LDClientMin';
6
12
 
@@ -79,6 +85,7 @@ it('includes context in variables for messages interpolation', async () => {
79
85
  const result = await client.config(key, testContext, defaultValue);
80
86
 
81
87
  expect(result.messages?.[0].content).toBe('User key: test-user');
88
+ expect(result.toVercelAISDK).toEqual(expect.any(Function));
82
89
  });
83
90
 
84
91
  it('handles missing metadata in variation', async () => {
@@ -132,3 +139,323 @@ it('passes the default value to the underlying client', async () => {
132
139
 
133
140
  expect(mockLdClient.variation).toHaveBeenCalledWith(key, testContext, defaultValue);
134
141
  });
142
+
143
+ // New agent-related tests
144
+ it('returns single agent config with interpolated instructions', async () => {
145
+ const client = new LDAIClientImpl(mockLdClient);
146
+ const key = 'test-agent';
147
+ const defaultValue: LDAIAgentDefaults = {
148
+ model: { name: 'test', parameters: { name: 'test-model' } },
149
+ instructions: 'You are a helpful assistant.',
150
+ enabled: true,
151
+ toVercelAISDK: <TMod>(
152
+ provider: VercelAISDKProvider<TMod> | Record<string, VercelAISDKProvider<TMod>>,
153
+ options?: VercelAISDKMapOptions,
154
+ ): VercelAISDKConfig<TMod> => {
155
+ const modelProvider = typeof provider === 'function' ? provider : provider.test;
156
+ return {
157
+ model: modelProvider('test-model'),
158
+ messages: [],
159
+ ...(options?.nonInterpolatedMessages
160
+ ? {
161
+ messages: options.nonInterpolatedMessages,
162
+ }
163
+ : {}),
164
+ };
165
+ },
166
+ };
167
+
168
+ const mockVariation = {
169
+ model: {
170
+ name: 'example-model',
171
+ parameters: { name: 'imagination', temperature: 0.7, maxTokens: 4096 },
172
+ },
173
+ provider: {
174
+ name: 'example-provider',
175
+ },
176
+ instructions: 'You are a helpful assistant. Your name is {{name}} and your score is {{score}}',
177
+ _ldMeta: {
178
+ variationKey: 'v1',
179
+ enabled: true,
180
+ mode: 'agent',
181
+ },
182
+ };
183
+
184
+ mockLdClient.variation.mockResolvedValue(mockVariation);
185
+
186
+ const variables = { name: 'John', score: 42 };
187
+ const result = await client.agent(key, testContext, defaultValue, variables);
188
+
189
+ expect(result).toEqual({
190
+ model: {
191
+ name: 'example-model',
192
+ parameters: { name: 'imagination', temperature: 0.7, maxTokens: 4096 },
193
+ },
194
+ provider: {
195
+ name: 'example-provider',
196
+ },
197
+ instructions: 'You are a helpful assistant. Your name is John and your score is 42',
198
+ tracker: expect.any(Object),
199
+ enabled: true,
200
+ toVercelAISDK: expect.any(Function),
201
+ });
202
+
203
+ // Verify tracking was called
204
+ expect(mockLdClient.track).toHaveBeenCalledWith(
205
+ '$ld:ai:agent:function:single',
206
+ testContext,
207
+ key,
208
+ 1,
209
+ );
210
+ });
211
+
212
+ it('includes context in variables for agent instructions interpolation', async () => {
213
+ const client = new LDAIClientImpl(mockLdClient);
214
+ const key = 'test-agent';
215
+ const defaultValue: LDAIAgentDefaults = {
216
+ model: { name: 'test', parameters: { name: 'test-model' } },
217
+ instructions: 'You are a helpful assistant.',
218
+ enabled: true,
219
+ toVercelAISDK: <TMod>(
220
+ provider: VercelAISDKProvider<TMod> | Record<string, VercelAISDKProvider<TMod>>,
221
+ options?: VercelAISDKMapOptions,
222
+ ): VercelAISDKConfig<TMod> => {
223
+ const modelProvider = typeof provider === 'function' ? provider : provider.test;
224
+ return {
225
+ model: modelProvider('test-model'),
226
+ messages: [],
227
+ ...(options?.nonInterpolatedMessages
228
+ ? {
229
+ messages: options.nonInterpolatedMessages,
230
+ }
231
+ : {}),
232
+ };
233
+ },
234
+ };
235
+
236
+ const mockVariation = {
237
+ instructions: 'You are a helpful assistant. Your user key is {{ldctx.key}}',
238
+ _ldMeta: { variationKey: 'v1', enabled: true, mode: 'agent' },
239
+ };
240
+
241
+ mockLdClient.variation.mockResolvedValue(mockVariation);
242
+
243
+ const result = await client.agent(key, testContext, defaultValue);
244
+
245
+ expect(result.instructions).toBe('You are a helpful assistant. Your user key is test-user');
246
+ });
247
+
248
+ it('handles missing metadata in agent variation', async () => {
249
+ const client = new LDAIClientImpl(mockLdClient);
250
+ const key = 'test-agent';
251
+ const defaultValue: LDAIAgentDefaults = {
252
+ model: { name: 'test', parameters: { name: 'test-model' } },
253
+ instructions: 'You are a helpful assistant.',
254
+ enabled: true,
255
+ toVercelAISDK: <TMod>(
256
+ provider: VercelAISDKProvider<TMod> | Record<string, VercelAISDKProvider<TMod>>,
257
+ options?: VercelAISDKMapOptions,
258
+ ): VercelAISDKConfig<TMod> => {
259
+ const modelProvider = typeof provider === 'function' ? provider : provider.test;
260
+ return {
261
+ model: modelProvider('test-model'),
262
+ messages: [],
263
+ ...(options?.nonInterpolatedMessages
264
+ ? {
265
+ messages: options.nonInterpolatedMessages,
266
+ }
267
+ : {}),
268
+ };
269
+ },
270
+ };
271
+
272
+ const mockVariation = {
273
+ model: { name: 'example-provider', parameters: { name: 'imagination' } },
274
+ instructions: 'Hello.',
275
+ };
276
+
277
+ mockLdClient.variation.mockResolvedValue(mockVariation);
278
+
279
+ const result = await client.agent(key, testContext, defaultValue);
280
+
281
+ expect(result).toEqual({
282
+ model: { name: 'example-provider', parameters: { name: 'imagination' } },
283
+ instructions: 'Hello.',
284
+ tracker: expect.any(Object),
285
+ enabled: false,
286
+ toVercelAISDK: expect.any(Function),
287
+ });
288
+ });
289
+
290
+ it('passes the default value to the underlying client for single agent', async () => {
291
+ const client = new LDAIClientImpl(mockLdClient);
292
+ const key = 'non-existent-agent';
293
+ const defaultValue: LDAIAgentDefaults = {
294
+ model: { name: 'default-model', parameters: { name: 'default' } },
295
+ provider: { name: 'default-provider' },
296
+ instructions: 'Default instructions',
297
+ enabled: true,
298
+ toVercelAISDK: <TMod>(
299
+ provider: VercelAISDKProvider<TMod> | Record<string, VercelAISDKProvider<TMod>>,
300
+ options?: VercelAISDKMapOptions,
301
+ ): VercelAISDKConfig<TMod> => {
302
+ const modelProvider =
303
+ typeof provider === 'function' ? provider : provider['default-provider'];
304
+ return {
305
+ model: modelProvider('default-model'),
306
+ messages: [],
307
+ ...(options?.nonInterpolatedMessages
308
+ ? {
309
+ messages: options.nonInterpolatedMessages,
310
+ }
311
+ : {}),
312
+ };
313
+ },
314
+ };
315
+
316
+ mockLdClient.variation.mockResolvedValue(defaultValue);
317
+
318
+ const result = await client.agent(key, testContext, defaultValue);
319
+
320
+ expect(result).toEqual({
321
+ model: defaultValue.model,
322
+ instructions: defaultValue.instructions,
323
+ provider: defaultValue.provider,
324
+ tracker: expect.any(Object),
325
+ enabled: false,
326
+ toVercelAISDK: expect.any(Function),
327
+ });
328
+
329
+ expect(mockLdClient.variation).toHaveBeenCalledWith(key, testContext, defaultValue);
330
+ });
331
+
332
+ it('returns multiple agents config with interpolated instructions', async () => {
333
+ const client = new LDAIClientImpl(mockLdClient);
334
+
335
+ const agentConfigs = [
336
+ {
337
+ key: 'research-agent',
338
+ defaultValue: {
339
+ model: { name: 'test', parameters: { name: 'test-model' } },
340
+ instructions: 'You are a research assistant.',
341
+ enabled: true,
342
+ toVercelAISDK: <TMod>(
343
+ provider: VercelAISDKProvider<TMod> | Record<string, VercelAISDKProvider<TMod>>,
344
+ options?: VercelAISDKMapOptions,
345
+ ): VercelAISDKConfig<TMod> => {
346
+ const modelProvider = typeof provider === 'function' ? provider : provider.test;
347
+ return {
348
+ model: modelProvider('test-model'),
349
+ messages: [],
350
+ ...(options?.nonInterpolatedMessages
351
+ ? {
352
+ messages: options.nonInterpolatedMessages,
353
+ }
354
+ : {}),
355
+ };
356
+ },
357
+ },
358
+ variables: { topic: 'climate change' },
359
+ },
360
+ {
361
+ key: 'writing-agent',
362
+ defaultValue: {
363
+ model: { name: 'test', parameters: { name: 'test-model' } },
364
+ instructions: 'You are a writing assistant.',
365
+ enabled: true,
366
+ toVercelAISDK: <TMod>(
367
+ provider: VercelAISDKProvider<TMod> | Record<string, VercelAISDKProvider<TMod>>,
368
+ options?: VercelAISDKMapOptions,
369
+ ): VercelAISDKConfig<TMod> => {
370
+ const modelProvider = typeof provider === 'function' ? provider : provider.test;
371
+ return {
372
+ model: modelProvider('test-model'),
373
+ messages: [],
374
+ ...(options?.nonInterpolatedMessages
375
+ ? {
376
+ messages: options.nonInterpolatedMessages,
377
+ }
378
+ : {}),
379
+ };
380
+ },
381
+ },
382
+ variables: { style: 'academic' },
383
+ },
384
+ ] as const;
385
+
386
+ const mockVariations = {
387
+ 'research-agent': {
388
+ model: {
389
+ name: 'research-model',
390
+ parameters: { temperature: 0.3, maxTokens: 2048 },
391
+ },
392
+ provider: { name: 'openai' },
393
+ instructions: 'You are a research assistant specializing in {{topic}}.',
394
+ _ldMeta: { variationKey: 'v1', enabled: true, mode: 'agent' },
395
+ },
396
+ 'writing-agent': {
397
+ model: {
398
+ name: 'writing-model',
399
+ parameters: { temperature: 0.7, maxTokens: 1024 },
400
+ },
401
+ provider: { name: 'anthropic' },
402
+ instructions: 'You are a writing assistant with {{style}} style.',
403
+ _ldMeta: { variationKey: 'v2', enabled: true, mode: 'agent' },
404
+ },
405
+ };
406
+
407
+ mockLdClient.variation.mockImplementation((key) =>
408
+ Promise.resolve(mockVariations[key as keyof typeof mockVariations]),
409
+ );
410
+
411
+ const result = await client.agents(agentConfigs, testContext);
412
+
413
+ expect(result).toEqual({
414
+ 'research-agent': {
415
+ model: {
416
+ name: 'research-model',
417
+ parameters: { temperature: 0.3, maxTokens: 2048 },
418
+ },
419
+ provider: { name: 'openai' },
420
+ instructions: 'You are a research assistant specializing in climate change.',
421
+ tracker: expect.any(Object),
422
+ enabled: true,
423
+ toVercelAISDK: expect.any(Function),
424
+ },
425
+ 'writing-agent': {
426
+ model: {
427
+ name: 'writing-model',
428
+ parameters: { temperature: 0.7, maxTokens: 1024 },
429
+ },
430
+ provider: { name: 'anthropic' },
431
+ instructions: 'You are a writing assistant with academic style.',
432
+ tracker: expect.any(Object),
433
+ enabled: true,
434
+ toVercelAISDK: expect.any(Function),
435
+ },
436
+ });
437
+
438
+ // Verify tracking was called
439
+ expect(mockLdClient.track).toHaveBeenCalledWith(
440
+ '$ld:ai:agent:function:multiple',
441
+ testContext,
442
+ agentConfigs.length,
443
+ agentConfigs.length,
444
+ );
445
+ });
446
+
447
+ it('handles empty agent configs array', async () => {
448
+ const client = new LDAIClientImpl(mockLdClient);
449
+
450
+ const result = await client.agents([], testContext);
451
+
452
+ expect(result).toEqual({});
453
+
454
+ // Verify tracking was called with 0 agents
455
+ expect(mockLdClient.track).toHaveBeenCalledWith(
456
+ '$ld:ai:agent:function:multiple',
457
+ testContext,
458
+ 0,
459
+ 0,
460
+ );
461
+ });