From f7a1d95ec479cf21129a345a803d0d38128875d2 Mon Sep 17 00:00:00 2001 From: dreamhunter2333 Date: Thu, 12 Mar 2026 20:25:45 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=B6=88=E9=99=A4=E5=89=A9?= =?UTF-8?q?=E4=BD=99=E9=87=8D=E5=A4=8D=E4=BB=A3=E7=A0=81=EF=BC=8C=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=20usage=20=E5=A4=84=E7=90=86=E5=92=8C=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E9=A3=8E=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 提取 checkoutResponsesUsageData 到 shared,消除两个 responses proxy 重复 - claude-to-openai 复用 usage-utils 的 normalizeUsage(支持 cached_tokens) - 删除 utils.ts 默认导出,统一使用命名导入 - channel-resolver JSON.parse 加防御,坏数据跳过不崩溃 Co-Authored-By: Claude Opus 4.6 --- src/admin/token_utils.ts | 4 +- src/db/index.ts | 12 +++--- src/providers/azure-openai-responses-proxy.ts | 19 +--------- src/providers/claude-to-openai-proxy.ts | 38 +++++-------------- src/providers/openai-responses-proxy.ts | 19 +--------- src/providers/shared/channel-resolver.ts | 6 ++- .../shared/responses-stream-utils.ts | 21 ++++++++++ src/utils.ts | 9 ----- 8 files changed, 48 insertions(+), 80 deletions(-) diff --git a/src/admin/token_utils.ts b/src/admin/token_utils.ts index 941bcab..6d9e3e8 100644 --- a/src/admin/token_utils.ts +++ b/src/admin/token_utils.ts @@ -1,7 +1,7 @@ import { Context } from "hono"; import { CONSTANTS } from "../constants"; -import utils from "../utils"; +import { getJsonSetting } from "../utils"; // Token 工具对象 export const TokenUtils = { @@ -24,7 +24,7 @@ export const TokenUtils = { } // Fallback to global pricing - const globalPricingMap = await utils.getJsonSetting(c, CONSTANTS.MODEL_PRICING_KEY); + const globalPricingMap = await getJsonSetting(c, CONSTANTS.MODEL_PRICING_KEY); return globalPricingMap?.[model] || null; }, diff --git a/src/db/index.ts b/src/db/index.ts index fe8028f..6a1906d 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -1,6 +1,6 @@ import { Context } from "hono"; import { CONSTANTS } from "../constants"; -import utils from "../utils"; +import { getSetting, saveSetting } from "../utils"; const DB_INIT_QUERIES = ` CREATE TABLE IF NOT EXISTS channel_config ( @@ -34,23 +34,23 @@ const dbOperations = { .join(";\n"); await c.env.DB.exec(query); - const version = await utils.getSetting(c, CONSTANTS.DB_VERSION_KEY); + const version = await getSetting(c, CONSTANTS.DB_VERSION_KEY); if (version) { return c.json({ message: "Database already initialized" }); } - await utils.saveSetting(c, CONSTANTS.DB_VERSION_KEY, CONSTANTS.DB_VERSION); + await saveSetting(c, CONSTANTS.DB_VERSION_KEY, CONSTANTS.DB_VERSION); }, migrate: async (c: Context) => { - const version = await utils.getSetting(c, CONSTANTS.DB_VERSION_KEY); + const version = await getSetting(c, CONSTANTS.DB_VERSION_KEY); if (version != CONSTANTS.DB_VERSION) { // TODO: Perform migration logic here // Update the version in the settings table - await utils.saveSetting(c, CONSTANTS.DB_VERSION_KEY, CONSTANTS.DB_VERSION); + await saveSetting(c, CONSTANTS.DB_VERSION_KEY, CONSTANTS.DB_VERSION); } }, getVersion: async (c: Context): Promise => { - return await utils.getSetting(c, CONSTANTS.DB_VERSION_KEY); + return await getSetting(c, CONSTANTS.DB_VERSION_KEY); } } diff --git a/src/providers/azure-openai-responses-proxy.ts b/src/providers/azure-openai-responses-proxy.ts index 4049466..f8fc814 100644 --- a/src/providers/azure-openai-responses-proxy.ts +++ b/src/providers/azure-openai-responses-proxy.ts @@ -1,9 +1,7 @@ import { Context } from "hono" import { handleStreamResponse, - OpenAIResponsesResponse, - extractUsageFromResponse, - estimateUsageFromBodies, + checkoutResponsesUsageData, } from "./shared/responses-stream-utils" const buildProxyRequest = ( @@ -58,20 +56,7 @@ export default { } if (response.ok) { - try { - const resJson = await response.clone().json() - const usage = extractUsageFromResponse(resJson) - if (usage) { - await saveUsage(usage) - } else { - const estimatedUsage = estimateUsageFromBodies(requestBody, resJson) - if (estimatedUsage) { - await saveUsage(estimatedUsage) - } - } - } catch (error) { - console.error("Error parsing response JSON for usage:", error) - } + await checkoutResponsesUsageData(saveUsage, response, requestBody) } return response diff --git a/src/providers/claude-to-openai-proxy.ts b/src/providers/claude-to-openai-proxy.ts index 030b682..490b1ad 100644 --- a/src/providers/claude-to-openai-proxy.ts +++ b/src/providers/claude-to-openai-proxy.ts @@ -1,12 +1,5 @@ import { Context } from "hono" - -type OpenAIUsageLike = { - prompt_tokens?: number; - completion_tokens?: number; - total_tokens?: number; - input_tokens?: number; - output_tokens?: number; -} +import { normalizeUsage as normalizeResponsesUsage } from "./shared/usage-utils" type OpenAIStreamChoice = { index?: number; @@ -30,7 +23,13 @@ type OpenAIStreamChunk = { id?: string; model?: string; choices?: OpenAIStreamChoice[]; - usage?: OpenAIUsageLike; + usage?: { + prompt_tokens?: number; + completion_tokens?: number; + total_tokens?: number; + input_tokens?: number; + output_tokens?: number; + }; } const mapFinishReasonToClaude = (finishReason?: string | null): string | null => { @@ -261,23 +260,6 @@ const convertOpenAIResponseToClaude = (resJson: any): any => { }; } -const normalizeUsage = (raw?: OpenAIUsageLike): Usage | null => { - if (!raw) return null; - const promptTokens = raw.prompt_tokens ?? raw.input_tokens; - const completionTokens = raw.completion_tokens ?? raw.output_tokens; - const totalTokens = raw.total_tokens ?? ( - (promptTokens ?? 0) + (completionTokens ?? 0) - ); - if (promptTokens == null && completionTokens == null && totalTokens == null) { - return null; - } - return { - prompt_tokens: promptTokens, - completion_tokens: completionTokens, - total_tokens: totalTokens, - }; -} - const createClaudeStreamTransformer = ( saveUsage: (usage: Usage) => Promise ): TransformStream => { @@ -441,7 +423,7 @@ const createClaudeStreamTransformer = ( if (!chunkJson) return; if (chunkJson.usage) { - const normalized = normalizeUsage(chunkJson.usage); + const normalized = normalizeResponsesUsage(chunkJson.usage); if (normalized) { usageToSave = normalized; outputTokens = normalized.completion_tokens ?? outputTokens; @@ -583,7 +565,7 @@ export default { if (response.ok) { try { const resJson = await response.clone().json>(); - const usage = normalizeUsage(resJson?.usage); + const usage = normalizeResponsesUsage(resJson?.usage); if (usage) { await saveUsage(usage); } diff --git a/src/providers/openai-responses-proxy.ts b/src/providers/openai-responses-proxy.ts index 248f1b5..c663002 100644 --- a/src/providers/openai-responses-proxy.ts +++ b/src/providers/openai-responses-proxy.ts @@ -1,9 +1,7 @@ import { Context } from "hono" import { handleStreamResponse, - OpenAIResponsesResponse, - extractUsageFromResponse, - estimateUsageFromBodies, + checkoutResponsesUsageData, } from "./shared/responses-stream-utils" const buildProxyRequest = ( @@ -51,20 +49,7 @@ export default { } if (response.ok) { - try { - const resJson = await response.clone().json() - const usage = extractUsageFromResponse(resJson) - if (usage) { - await saveUsage(usage) - } else { - const estimatedUsage = estimateUsageFromBodies(requestBody, resJson) - if (estimatedUsage) { - await saveUsage(estimatedUsage) - } - } - } catch (error) { - console.error("Error parsing response JSON for usage:", error) - } + await checkoutResponsesUsageData(saveUsage, response, requestBody) } return response diff --git a/src/providers/shared/channel-resolver.ts b/src/providers/shared/channel-resolver.ts index ec2e194..dd75fe2 100644 --- a/src/providers/shared/channel-resolver.ts +++ b/src/providers/shared/channel-resolver.ts @@ -53,7 +53,11 @@ export const resolveChannel = async ( const availableChannels: Array<{ key: string, config: ChannelConfig, mapping: { pattern: string, deployment: string } }> = []; for (const row of channelsResult.results) { - const config = JSON.parse(row.value) as ChannelConfig; + const config = (() => { try { return JSON.parse(row.value) as ChannelConfig; } catch { return null; } })(); + if (!config) { + console.error(`Invalid channel config for key: ${row.key}`); + continue; + } if (allowedTypes && (!config.type || !allowedTypes.includes(config.type))) { continue; diff --git a/src/providers/shared/responses-stream-utils.ts b/src/providers/shared/responses-stream-utils.ts index c2aa76c..fe1586a 100644 --- a/src/providers/shared/responses-stream-utils.ts +++ b/src/providers/shared/responses-stream-utils.ts @@ -95,4 +95,25 @@ export const handleStreamResponse = async ( } } +export const checkoutResponsesUsageData = async ( + saveUsage: (usage: Usage) => Promise, + response: Response, + requestBody: any, +): Promise => { + try { + const resJson = await response.clone().json() + const usage = extractUsageFromResponse(resJson) + if (usage) { + await saveUsage(usage) + } else { + const estimatedUsage = estimateUsageFromBodies(requestBody, resJson) + if (estimatedUsage) { + await saveUsage(estimatedUsage) + } + } + } catch (error) { + console.error("Error parsing response JSON for usage:", error) + } +} + export { OpenAIResponsesResponse, extractUsageFromResponse, estimateUsageFromBodies } diff --git a/src/utils.ts b/src/utils.ts index 1631f52..ab1ac34 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -106,12 +106,3 @@ export const findDeploymentMapping = ( return null; } - -export default { - getJsonObjectValue, - getJsonSetting, - getSetting, - saveSetting, - wildcardMatch, - findDeploymentMapping, -}