1- import { createOpenAICompatible } from '@ai-sdk/openai-compatible'
1+ import path from 'path'
2+
3+ import { OpenAICompatibleChatLanguageModel } from '@ai-sdk/openai-compatible'
24import { streamText , APICallError , generateText , generateObject } from 'ai'
35
46import { PROFIT_MARGIN } from '../../../common/src/old-constants'
@@ -20,24 +22,74 @@ import type {
2022} from '../../../common/src/types/contracts/llm'
2123import type { ParamsOf } from '../../../common/src/types/function-params'
2224import type { LanguageModelV2 } from '@ai-sdk/provider'
23- import type {
24- OpenRouterProviderOptions ,
25- OpenRouterUsageAccounting ,
26- } from '@openrouter/ai-sdk-provider'
25+ import type { Logger } from '@codebuff/common/types/contracts/logger'
26+ import type { OpenRouterProviderOptions } from '@openrouter/ai-sdk-provider'
2727import type z from 'zod/v4'
2828
29+ // Forked from https://github.com/OpenRouterTeam/ai-sdk-provider/
30+ type OpenRouterUsageAccounting = {
31+ cost : number | null
32+ costDetails : {
33+ upstreamInferenceCost : number | null
34+ }
35+ }
36+
37+ function calculateUsedCredits ( params : { costDollars : number } ) : number {
38+ const { costDollars } = params
39+
40+ return Math . round ( costDollars * ( 1 + PROFIT_MARGIN ) * 100 )
41+ }
42+
2943function getAiSdkModel ( params : {
3044 apiKey : string
3145 model : string
46+ logger : Logger
3247} ) : LanguageModelV2 {
33- const { apiKey, model } = params
48+ const { apiKey, model, logger } = params
49+
50+ const openrouterUsage : OpenRouterUsageAccounting = {
51+ cost : null ,
52+ costDetails : {
53+ upstreamInferenceCost : null ,
54+ } ,
55+ }
3456
35- return createOpenAICompatible ( {
36- name : 'codebuff' ,
37- apiKey,
38- baseURL : WEBSITE_URL + '/api/v1' ,
57+ const codebuffBackendModel = new OpenAICompatibleChatLanguageModel ( model , {
58+ provider : 'codebuff.chat' ,
59+ url : ( { path : endpoint } ) =>
60+ new URL ( path . join ( '/api/v1' , endpoint ) , WEBSITE_URL ) . toString ( ) ,
61+ headers : ( ) => ( {
62+ Authorization : `Bearer ${ apiKey } ` ,
63+ 'user-agent' : `ai-sdk/codebuff/${ process . env . NEXT_PUBLIC_NPM_APP_VERSION || 'unknown-version' } ` ,
64+ } ) ,
65+ metadataExtractor : {
66+ extractMetadata : async ( ...inputs ) => {
67+ console . log ( inputs , 'extractMetadata' )
68+ return undefined
69+ } ,
70+ createStreamExtractor : ( ) => ( {
71+ processChunk : ( parsedChunk : any ) => {
72+ if ( typeof parsedChunk ?. usage ?. cost === 'number' ) {
73+ openrouterUsage . cost = parsedChunk . usage . cost
74+ }
75+ if (
76+ typeof parsedChunk ?. usage ?. cost_details ?. upstream_inference_cost ===
77+ 'number'
78+ ) {
79+ openrouterUsage . costDetails . upstreamInferenceCost =
80+ parsedChunk . usage . cost_details . upstream_inference_cost
81+ }
82+ } ,
83+ buildMetadata : ( ) => {
84+ return { codebuff : { usage : openrouterUsage } }
85+ } ,
86+ } ) ,
87+ } ,
88+ fetch : undefined ,
89+ includeUsage : undefined ,
3990 supportsStructuredOutputs : true ,
40- } ) ( model )
91+ } )
92+ return codebuffBackendModel
4193}
4294
4395export async function * promptAiSdkStream (
@@ -172,28 +224,12 @@ export async function* promptAiSdkStream(
172224 }
173225
174226 const providerMetadata = ( await response . providerMetadata ) ?? { }
175- const usage = await response . usage
176- let inputTokens = usage . inputTokens || 0
177- let cacheReadInputTokens : number = 0
178- let cacheCreationInputTokens : number = 0
227+
179228 let costOverrideDollars : number | undefined
180- if ( providerMetadata . anthropic ) {
181- cacheReadInputTokens =
182- typeof providerMetadata . anthropic . cacheReadInputTokens === 'number'
183- ? providerMetadata . anthropic . cacheReadInputTokens
184- : 0
185- cacheCreationInputTokens =
186- typeof providerMetadata . anthropic . cacheCreationInputTokens === 'number'
187- ? providerMetadata . anthropic . cacheCreationInputTokens
188- : 0
189- }
190- if ( providerMetadata . openrouter ) {
191- if ( providerMetadata . openrouter . usage ) {
192- const openrouterUsage = providerMetadata . openrouter
229+ if ( providerMetadata . codebuff ) {
230+ if ( providerMetadata . codebuff . usage ) {
231+ const openrouterUsage = providerMetadata . codebuff
193232 . usage as OpenRouterUsageAccounting
194- cacheReadInputTokens =
195- openrouterUsage . promptTokensDetails ?. cachedTokens ?? 0
196- inputTokens = openrouterUsage . promptTokens - cacheReadInputTokens
197233
198234 costOverrideDollars =
199235 ( openrouterUsage . cost ?? 0 ) +
@@ -205,8 +241,9 @@ export async function* promptAiSdkStream(
205241
206242 // Call the cost callback if provided
207243 if ( params . onCostCalculated && costOverrideDollars ) {
208- const creditsUsed = costOverrideDollars * ( 1 + PROFIT_MARGIN )
209- await params . onCostCalculated ( creditsUsed )
244+ await params . onCostCalculated (
245+ calculateUsedCredits ( { costDollars : costOverrideDollars } ) ,
246+ )
210247 }
211248
212249 return messageId
@@ -229,7 +266,6 @@ export async function promptAiSdk(
229266 return ''
230267 }
231268
232- const startTime = Date . now ( )
233269 let aiSDKModel = getAiSdkModel ( params )
234270
235271 const response = await generateText ( {
@@ -248,31 +284,12 @@ export async function promptAiSdk(
248284 } )
249285 const content = response . text
250286
251- const messageId = response . response . id
252287 const providerMetadata = response . providerMetadata ?? { }
253- const usage = response . usage
254- let inputTokens = usage . inputTokens || 0
255- const outputTokens = usage . outputTokens || 0
256- let cacheReadInputTokens : number = 0
257- let cacheCreationInputTokens : number = 0
258288 let costOverrideDollars : number | undefined
259- if ( providerMetadata . anthropic ) {
260- cacheReadInputTokens =
261- typeof providerMetadata . anthropic . cacheReadInputTokens === 'number'
262- ? providerMetadata . anthropic . cacheReadInputTokens
263- : 0
264- cacheCreationInputTokens =
265- typeof providerMetadata . anthropic . cacheCreationInputTokens === 'number'
266- ? providerMetadata . anthropic . cacheCreationInputTokens
267- : 0
268- }
269- if ( providerMetadata . openrouter ) {
270- if ( providerMetadata . openrouter . usage ) {
271- const openrouterUsage = providerMetadata . openrouter
289+ if ( providerMetadata . codebuff ) {
290+ if ( providerMetadata . codebuff . usage ) {
291+ const openrouterUsage = providerMetadata . codebuff
272292 . usage as OpenRouterUsageAccounting
273- cacheReadInputTokens =
274- openrouterUsage . promptTokensDetails ?. cachedTokens ?? 0
275- inputTokens = openrouterUsage . promptTokens - cacheReadInputTokens
276293
277294 costOverrideDollars =
278295 ( openrouterUsage . cost ?? 0 ) +
@@ -282,8 +299,9 @@ export async function promptAiSdk(
282299
283300 // Call the cost callback if provided
284301 if ( params . onCostCalculated && costOverrideDollars ) {
285- const creditsUsed = costOverrideDollars * ( 1 + PROFIT_MARGIN )
286- await params . onCostCalculated ( creditsUsed )
302+ await params . onCostCalculated (
303+ calculateUsedCredits ( { costDollars : costOverrideDollars } ) ,
304+ )
287305 }
288306
289307 return content
@@ -325,31 +343,12 @@ export async function promptAiSdkStructured<T>(
325343
326344 const content = response . object
327345
328- const messageId = response . response . id
329346 const providerMetadata = response . providerMetadata ?? { }
330- const usage = response . usage
331- let inputTokens = usage . inputTokens || 0
332- const outputTokens = usage . outputTokens || 0
333- let cacheReadInputTokens : number = 0
334- let cacheCreationInputTokens : number = 0
335347 let costOverrideDollars : number | undefined
336- if ( providerMetadata . anthropic ) {
337- cacheReadInputTokens =
338- typeof providerMetadata . anthropic . cacheReadInputTokens === 'number'
339- ? providerMetadata . anthropic . cacheReadInputTokens
340- : 0
341- cacheCreationInputTokens =
342- typeof providerMetadata . anthropic . cacheCreationInputTokens === 'number'
343- ? providerMetadata . anthropic . cacheCreationInputTokens
344- : 0
345- }
346- if ( providerMetadata . openrouter ) {
347- if ( providerMetadata . openrouter . usage ) {
348- const openrouterUsage = providerMetadata . openrouter
348+ if ( providerMetadata . codebuff ) {
349+ if ( providerMetadata . codebuff . usage ) {
350+ const openrouterUsage = providerMetadata . codebuff
349351 . usage as OpenRouterUsageAccounting
350- cacheReadInputTokens =
351- openrouterUsage . promptTokensDetails ?. cachedTokens ?? 0
352- inputTokens = openrouterUsage . promptTokens - cacheReadInputTokens
353352
354353 costOverrideDollars =
355354 ( openrouterUsage . cost ?? 0 ) +
@@ -359,8 +358,9 @@ export async function promptAiSdkStructured<T>(
359358
360359 // Call the cost callback if provided
361360 if ( params . onCostCalculated && costOverrideDollars ) {
362- const creditsUsed = costOverrideDollars * ( 1 + PROFIT_MARGIN )
363- await params . onCostCalculated ( creditsUsed )
361+ await params . onCostCalculated (
362+ calculateUsedCredits ( { costDollars : costOverrideDollars } ) ,
363+ )
364364 }
365365
366366 return content
0 commit comments