Skip to content

Commit 714cef6

Browse files
committed
Support only gpt-5. Compute cost
1 parent d017452 commit 714cef6

File tree

2 files changed

+49
-49
lines changed

2 files changed

+49
-49
lines changed

web/src/app/api/v1/chat/completions/_post.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
handleOpenRouterNonStream,
2121
handleOpenRouterStream,
2222
} from '@/llm-api/openrouter'
23-
import { handleOpenAIStream } from '@/llm-api/openai'
23+
import { handleOpenAIStream, OPENAI_SUPPORTED_MODELS } from '@/llm-api/openai'
2424
import { extractApiKeyFromHeader } from '@/util/auth'
2525

2626
export async function postChatCompletions(params: {
@@ -206,8 +206,12 @@ export async function postChatCompletions(params: {
206206
if (bodyStream) {
207207
// Streaming request
208208
const model = (body as any)?.model
209+
const shortModelName =
210+
typeof model === 'string' ? model.split('/')[1] : undefined
209211
const isOpenAIDirectModel =
210-
typeof model === 'string' && model.startsWith('openai/')
212+
typeof model === 'string' &&
213+
model.startsWith('openai/') &&
214+
OPENAI_SUPPORTED_MODELS.includes(shortModelName as any)
211215
const stream = await (isOpenAIDirectModel
212216
? handleOpenAIStream({
213217
body,

web/src/llm-api/openai.ts

Lines changed: 43 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ import { env } from '@codebuff/internal/env'
77
import type { InsertMessageBigqueryFn } from '@codebuff/common/types/contracts/bigquery'
88
import type { Logger } from '@codebuff/common/types/contracts/logger'
99

10+
export const OPENAI_SUPPORTED_MODELS = ['gpt-5'] as const
11+
export type OpenAIModel = (typeof OPENAI_SUPPORTED_MODELS)[number]
12+
13+
const INPUT_TOKEN_COSTS: Record<OpenAIModel, number> = {
14+
'gpt-5': 1.25,
15+
} as const
16+
const CACHED_INPUT_TOKEN_COSTS: Record<OpenAIModel, number> = {
17+
'gpt-5': 0.125,
18+
} as const
19+
const OUTPUT_TOKEN_COSTS: Record<OpenAIModel, number> = {
20+
'gpt-5': 10,
21+
} as const
22+
1023
type StreamState = { responseText: string; reasoningText: string }
1124

1225
function extractRequestMetadata(params: { body: unknown; logger: Logger }) {
@@ -25,11 +38,6 @@ function extractRequestMetadata(params: { body: unknown; logger: Logger }) {
2538
return { clientId, clientRequestId }
2639
}
2740

28-
function normalizeOpenAIModel(model: unknown): string | undefined {
29-
if (typeof model !== 'string') return undefined
30-
return model.startsWith('openai/') ? model.slice('openai/'.length) : model
31-
}
32-
3341
type OpenAIUsage = {
3442
prompt_tokens?: number
3543
prompt_tokens_details?: { cached_tokens?: number } | null
@@ -41,41 +49,19 @@ type OpenAIUsage = {
4149
cost_details?: { upstream_inference_cost?: number | null } | null
4250
}
4351

44-
function getOpenAIRatesPerMTokens(model: string): {
45-
inUsd: number
46-
outUsd: number
47-
} {
48-
const m = model.toLowerCase()
49-
if (
50-
m.includes('gpt-4o-mini') ||
51-
m.includes('4o-mini') ||
52-
m.includes('o4-mini')
53-
) {
54-
return { inUsd: 0.15, outUsd: 0.6 }
55-
}
56-
if (m.includes('gpt-4o')) {
57-
return { inUsd: 2.5, outUsd: 10 }
58-
}
59-
if (m.includes('gpt-4.1')) {
60-
return { inUsd: 5, outUsd: 15 }
61-
}
62-
if (m.startsWith('o3-pro')) {
63-
return { inUsd: 5, outUsd: 15 }
64-
}
65-
if (m.startsWith('o3')) {
66-
return { inUsd: 5, outUsd: 15 }
67-
}
68-
if (m.startsWith('gpt-5')) {
69-
return { inUsd: 5, outUsd: 15 }
70-
}
71-
return { inUsd: 2.5, outUsd: 10 }
72-
}
52+
function computeCostDollars(usage: OpenAIUsage, model: OpenAIModel): number {
53+
const inputTokenCost = INPUT_TOKEN_COSTS[model]
54+
const cachedInputTokenCost = CACHED_INPUT_TOKEN_COSTS[model]
55+
const outputTokenCost = OUTPUT_TOKEN_COSTS[model]
7356

74-
function computeCostDollars(usage: OpenAIUsage, model: string): number {
75-
const { inUsd, outUsd } = getOpenAIRatesPerMTokens(model)
7657
const inTokens = usage.prompt_tokens ?? 0
58+
const cachedInTokens = usage.prompt_tokens_details?.cached_tokens ?? 0
7759
const outTokens = usage.completion_tokens ?? 0
78-
return (inTokens / 1_000_000) * inUsd + (outTokens / 1_000_000) * outUsd
60+
return (
61+
(inTokens / 1_000_000) * inputTokenCost +
62+
(cachedInTokens / 1_000_000) * cachedInputTokenCost +
63+
(outTokens / 1_000_000) * outputTokenCost
64+
)
7965
}
8066

8167
export async function handleOpenAIStream({
@@ -96,10 +82,24 @@ export async function handleOpenAIStream({
9682
const startTime = new Date()
9783
const { clientId, clientRequestId } = extractRequestMetadata({ body, logger })
9884

99-
const model = normalizeOpenAIModel((body as any)?.model)
85+
const { model } = body
86+
const modelShortName =
87+
typeof model === 'string' ? model.split('/')[1] : undefined
88+
if (
89+
!modelShortName ||
90+
!OPENAI_SUPPORTED_MODELS.includes(modelShortName as OpenAIModel)
91+
) {
92+
throw new Error(
93+
`Unsupported OpenAI model: ${model} (supported models include only: ${OPENAI_SUPPORTED_MODELS.map((m) => `'${m}'`).join(', ')})`,
94+
)
95+
}
10096

10197
// Build OpenAI-compatible body
102-
const openaiBody: Record<string, unknown> = { ...body, model, stream: true }
98+
const openaiBody: Record<string, unknown> = {
99+
...body,
100+
model: modelShortName,
101+
stream: true,
102+
}
103103
// Ensure usage in final chunk
104104
const streamOptions = (openaiBody.stream_options as any) ?? {}
105105
streamOptions.include_usage = true
@@ -182,6 +182,7 @@ export async function handleOpenAIStream({
182182
startTime,
183183
request: openaiBody,
184184
line,
185+
modelShortName: modelShortName as OpenAIModel,
185186
state,
186187
logger,
187188
insertMessage: insertMessageBigquery,
@@ -239,6 +240,7 @@ async function handleOpenAILine({
239240
clientId,
240241
clientRequestId,
241242
startTime,
243+
modelShortName,
242244
request,
243245
line,
244246
state,
@@ -250,6 +252,7 @@ async function handleOpenAILine({
250252
clientId: string | null
251253
clientRequestId: string | null
252254
startTime: Date
255+
modelShortName: OpenAIModel
253256
request: unknown
254257
line: string
255258
state: StreamState
@@ -292,14 +295,7 @@ async function handleOpenAILine({
292295
// If usage present, it's the final chunk. Compute cost, log, and consume credits.
293296
if (obj && obj.usage) {
294297
const usage: OpenAIUsage = obj.usage
295-
const model: string =
296-
typeof obj.model === 'string'
297-
? obj.model
298-
: typeof (request as any)?.model === 'string'
299-
? (request as any).model
300-
: ''
301-
302-
const cost = computeCostDollars(usage, model)
298+
const cost = computeCostDollars(usage, modelShortName)
303299
obj.usage.cost = cost
304300
obj.usage.cost_details = { upstream_inference_cost: null }
305301

0 commit comments

Comments
 (0)