@@ -10,30 +10,53 @@ import type {
1010 LDTokenUsage ,
1111} from '@launchdarkly/server-sdk-ai' ;
1212
13+ import type {
14+ ModelUsageTokens ,
15+ StreamResponse ,
16+ TextResponse ,
17+ VercelAIModelParameters ,
18+ VercelAISDKConfig ,
19+ VercelAISDKMapOptions ,
20+ VercelAISDKProvider ,
21+ } from './types' ;
22+
1323/**
1424 * Vercel AI implementation of AIProvider.
1525 * This provider integrates Vercel AI SDK with LaunchDarkly's tracking capabilities.
1626 */
1727export class VercelProvider extends AIProvider {
1828 private _model : LanguageModel ;
19- private _parameters : Record < string , unknown > ;
29+ private _parameters : VercelAIModelParameters ;
2030
21- constructor ( model : LanguageModel , parameters : Record < string , unknown > , logger ?: LDLogger ) {
31+ /**
32+ * Constructor for the VercelProvider.
33+ * @param model - The Vercel AI model to use.
34+ * @param parameters - The Vercel AI model parameters.
35+ * @param logger - The logger to use for the Vercel AI provider.
36+ */
37+ constructor ( model : LanguageModel , parameters : VercelAIModelParameters , logger ?: LDLogger ) {
2238 super ( logger ) ;
2339 this . _model = model ;
2440 this . _parameters = parameters ;
2541 }
2642
2743 // =============================================================================
28- // MAIN FACTORY METHOD
44+ // MAIN FACTORY METHODS
2945 // =============================================================================
3046
3147 /**
3248 * Static factory method to create a Vercel AIProvider from an AI configuration.
49+ * This method auto-detects the provider and creates the model.
50+ * Note: Messages from the AI config are not included in the provider - messages
51+ * should be passed at invocation time via invokeModel().
52+ *
53+ * @param aiConfig The LaunchDarkly AI configuration
54+ * @param logger Optional logger
55+ * @returns A Promise that resolves to a configured VercelProvider
3356 */
3457 static async create ( aiConfig : LDAIConfig , logger ?: LDLogger ) : Promise < VercelProvider > {
3558 const model = await VercelProvider . createVercelModel ( aiConfig ) ;
36- const parameters = aiConfig . model ?. parameters || { } ;
59+ const parameters = VercelProvider . mapParameters ( aiConfig . model ?. parameters ) ;
3760 return new VercelProvider ( model , parameters , logger ) ;
3861 }
3962
@@ -45,23 +68,18 @@ export class VercelProvider extends AIProvider {
4568 * Invoke the Vercel AI model with an array of messages.
4669 */
4770 async invokeModel ( messages : LDMessage [ ] ) : Promise < ChatResponse > {
48- // Call Vercel AI generateText
49- // Type assertion: our MinLanguageModel is compatible with the expected LanguageModel interface
50- // The generateText function will work with any object that has the required properties
5171 const result = await generateText ( {
5272 model : this . _model ,
5373 messages,
5474 ...this . _parameters ,
5575 } ) ;
5676
57- // Create the assistant message
5877 const assistantMessage : LDMessage = {
5978 role : 'assistant' ,
6079 content : result . text ,
6180 } ;
6281
63- // Extract metrics including token usage and success status
64- const metrics = VercelProvider . createAIMetrics ( result ) ;
82+ const metrics = VercelProvider . getAIMetricsFromResponse ( result ) ;
6583
6684 return {
6785 message : assistantMessage ,
@@ -95,45 +113,220 @@ export class VercelProvider extends AIProvider {
95113 return mapping [ lowercasedName ] || lowercasedName ;
96114 }
97115
116+ /**
117+ * Map Vercel AI SDK usage data to LaunchDarkly token usage.
118+ *
119+ * @param usageData Usage data from Vercel AI SDK
120+ * @returns LDTokenUsage
121+ */
122+ static mapUsageDataToLDTokenUsage ( usageData : ModelUsageTokens ) : LDTokenUsage {
123+ // Support v4 field names (promptTokens, completionTokens) for backward compatibility
124+ const { totalTokens, inputTokens, outputTokens, promptTokens, completionTokens } = usageData ;
125+ return {
126+ total : totalTokens ?? 0 ,
127+ input : inputTokens ?? promptTokens ?? 0 ,
128+ output : outputTokens ?? completionTokens ?? 0 ,
129+ } ;
130+ }
131+
132+ /**
133+ * Get AI metrics from a Vercel AI SDK text response
134+ * This method extracts token usage information and success status from Vercel AI responses
135+ * and returns a LaunchDarkly AIMetrics object.
136+ * Supports both v4 and v5 field names for backward compatibility.
137+ *
138+ * @param response The response from generateText() or similar non-streaming operations
139+ * @returns LDAIMetrics with success status and token usage
140+ *
141+ * @example
142+ * const response = await aiConfig.tracker.trackMetricsOf(
143+ * VercelProvider.getAIMetricsFromResponse,
144+ * () => generateText(vercelConfig)
145+ * );
146+ */
147+ static getAIMetricsFromResponse ( response : TextResponse ) : LDAIMetrics {
148+ const finishReason = response ?. finishReason ?? 'unknown' ;
149+
150+ // favor totalUsage over usage for cumulative usage across all steps
151+ let usage : LDTokenUsage | undefined ;
152+ if ( response ?. totalUsage ) {
153+ usage = VercelProvider . mapUsageDataToLDTokenUsage ( response . totalUsage ) ;
154+ } else if ( response ?. usage ) {
155+ usage = VercelProvider . mapUsageDataToLDTokenUsage ( response . usage ) ;
156+ }
157+
158+ const success = finishReason !== 'error' ;
159+
160+ return {
161+ success,
162+ usage,
163+ } ;
164+ }
165+
98166 /**
99167 * Create AI metrics information from a Vercel AI response.
100168 * This method extracts token usage information and success status from Vercel AI responses
101169 * and returns a LaunchDarkly AIMetrics object.
102170 * Supports both v4 and v5 field names for backward compatibility.
171+ *
172+ * @deprecated Use `getAIMetricsFromResponse()` instead.
173+ * @param vercelResponse The response from generateText() or similar non-streaming operations
174+ * @returns LDAIMetrics with success status and token usage
175+ */
176+ static createAIMetrics ( vercelResponse : TextResponse ) : LDAIMetrics {
177+ return VercelProvider . getAIMetricsFromResponse ( vercelResponse ) ;
178+ }
179+
180+ /**
181+ * Get AI metrics from a Vercel AI SDK streaming result.
182+ *
183+ * This method waits for the stream to complete, then extracts metrics using totalUsage
184+ * (preferred for cumulative usage across all steps) or usage if totalUsage is unavailable.
185+ *
186+ * @param stream The stream result from streamText()
187+ * @returns A Promise that resolves to LDAIMetrics
188+ *
189+ * @example
190+ * const stream = aiConfig.tracker.trackStreamMetricsOf(
191+ * () => streamText(vercelConfig),
192+ * VercelProvider.getAIMetricsFromStream
193+ * );
103194 */
104- static createAIMetrics ( vercelResponse : any ) : LDAIMetrics {
105- // Extract token usage if available
195+ static async getAIMetricsFromStream ( stream : StreamResponse ) : Promise < LDAIMetrics > {
196+ const finishReason = ( await stream . finishReason ?. catch ( ( ) => 'error' ) ) ?? 'unknown' ;
197+
198+ // favor totalUsage over usage for cumulative usage across all steps
106199 let usage : LDTokenUsage | undefined ;
107- if ( vercelResponse ?. usage ) {
108- const { totalTokens, inputTokens, promptTokens, outputTokens, completionTokens } =
109- vercelResponse . usage ;
110- usage = {
111- total : totalTokens ?? 0 ,
112- input : inputTokens ?? promptTokens ?? 0 ,
113- output : outputTokens ?? completionTokens ?? 0 ,
114- } ;
200+ if ( stream . totalUsage ) {
201+ const usageData = await stream . totalUsage ;
202+ usage = VercelProvider . mapUsageDataToLDTokenUsage ( usageData ) ;
203+ } else if ( stream . usage ) {
204+ const usageData = await stream . usage ;
205+ usage = VercelProvider . mapUsageDataToLDTokenUsage ( usageData ) ;
115206 }
116207
117- // Vercel AI responses that complete successfully are considered successful
208+ const success = finishReason !== 'error' ;
209+
118210 return {
119- success : true ,
211+ success,
120212 usage,
121213 } ;
122214 }
123215
216+ /**
217+ * Map LaunchDarkly model parameters to Vercel AI SDK parameters.
218+ *
219+ * Parameter mappings:
220+ * - max_tokens → maxTokens
221+ * - max_completion_tokens → maxOutputTokens
222+ * - temperature → temperature
223+ * - top_p → topP
224+ * - top_k → topK
225+ * - presence_penalty → presencePenalty
226+ * - frequency_penalty → frequencyPenalty
227+ * - stop → stopSequences
228+ * - seed → seed
229+ *
230+ * @param parameters The LaunchDarkly model parameters to map
231+ * @returns An object containing mapped Vercel AI SDK parameters
232+ */
233+ static mapParameters ( parameters ?: { [ index : string ] : unknown } ) : VercelAIModelParameters {
234+ if ( ! parameters ) {
235+ return { } ;
236+ }
237+
238+ const params : VercelAIModelParameters = { } ;
239+
240+ if ( parameters . max_tokens !== undefined ) {
241+ params . maxTokens = parameters . max_tokens as number ;
242+ }
243+ if ( parameters . max_completion_tokens !== undefined ) {
244+ params . maxOutputTokens = parameters . max_completion_tokens as number ;
245+ }
246+ if ( parameters . temperature !== undefined ) {
247+ params . temperature = parameters . temperature as number ;
248+ }
249+ if ( parameters . top_p !== undefined ) {
250+ params . topP = parameters . top_p as number ;
251+ }
252+ if ( parameters . top_k !== undefined ) {
253+ params . topK = parameters . top_k as number ;
254+ }
255+ if ( parameters . presence_penalty !== undefined ) {
256+ params . presencePenalty = parameters . presence_penalty as number ;
257+ }
258+ if ( parameters . frequency_penalty !== undefined ) {
259+ params . frequencyPenalty = parameters . frequency_penalty as number ;
260+ }
261+ if ( parameters . stop !== undefined ) {
262+ params . stopSequences = parameters . stop as string [ ] ;
263+ }
264+ if ( parameters . seed !== undefined ) {
265+ params . seed = parameters . seed as number ;
266+ }
267+
268+ return params ;
269+ }
270+
271+ /**
272+ * Convert an AI configuration to Vercel AI SDK parameters.
273+ * This static method allows converting an LDAIConfig to VercelAISDKConfig without
274+ * requiring an instance of VercelProvider.
275+ *
276+ * @param aiConfig The LaunchDarkly AI configuration
277+ * @param provider A Vercel AI SDK Provider or a map of provider names to Vercel AI SDK Providers
278+ * @param options Optional mapping options
279+ * @returns A configuration directly usable in Vercel AI SDK generateText() and streamText()
280+ * @throws {Error } if a Vercel AI SDK model cannot be determined from the given provider parameter
281+ */
282+ static toVercelAISDK < TMod > (
283+ aiConfig : LDAIConfig ,
284+ provider : VercelAISDKProvider < TMod > | Record < string , VercelAISDKProvider < TMod > > ,
285+ options ?: VercelAISDKMapOptions | undefined ,
286+ ) : VercelAISDKConfig < TMod > {
287+ // Determine the model from the provider
288+ let model : TMod | undefined ;
289+ if ( typeof provider === 'function' ) {
290+ model = provider ( aiConfig . model ?. name ?? '' ) ;
291+ } else {
292+ model = provider [ aiConfig . provider ?. name ?? '' ] ?.( aiConfig . model ?. name ?? '' ) ;
293+ }
294+ if ( ! model ) {
295+ throw new Error (
296+ 'Vercel AI SDK model cannot be determined from the supplied provider parameter.' ,
297+ ) ;
298+ }
299+
300+ // Merge messages from config and options
301+ let messages : LDMessage [ ] | undefined ;
302+ const configMessages = ( 'messages' in aiConfig ? aiConfig . messages : undefined ) as
303+ | LDMessage [ ]
304+ | undefined ;
305+ if ( configMessages || options ?. nonInterpolatedMessages ) {
306+ messages = [ ...( configMessages ?? [ ] ) , ...( options ?. nonInterpolatedMessages ?? [ ] ) ] ;
307+ }
308+
309+ // Map parameters using the shared mapping method
310+ const params = VercelProvider . mapParameters ( aiConfig . model ?. parameters ) ;
311+
312+ // Build and return the Vercel AI SDK configuration
313+ return {
314+ model,
315+ messages,
316+ ...params ,
317+ } ;
318+ }
319+
124320 /**
125321 * Create a Vercel AI model from an AI configuration.
126- * This method creates a Vercel AI model based on the provider configuration .
322+ * This method auto-detects the provider and creates the model instance .
127323 *
128324 * @param aiConfig The LaunchDarkly AI configuration
129325 * @returns A Promise that resolves to a configured Vercel AI model
130326 */
131327 static async createVercelModel ( aiConfig : LDAIConfig ) : Promise < LanguageModel > {
132328 const providerName = VercelProvider . mapProvider ( aiConfig . provider ?. name || '' ) ;
133329 const modelName = aiConfig . model ?. name || '' ;
134- // Parameters are not used in model creation but kept for future use
135- // eslint-disable-next-line @typescript-eslint/no-unused-vars
136- const parameters = aiConfig . model ?. parameters || { } ;
137330
138331 // Map provider names to their corresponding Vercel AI SDK imports
139332 switch ( providerName ) {
0 commit comments