refactor: 消除剩余重复代码,统一 usage 处理和导入风格

- 提取 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 <noreply@anthropic.com>
This commit is contained in:
dreamhunter2333andClaude Opus 4.6 committed 2026-03-12 20:25:45 +08:00
1 parent 07a597e41a
commit f7a1d95ec4
8 files changed
+48 -80

No files matched your search

+2 -2
View File
@@ -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;
},
+6 -6
View File
@@ -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<HonoCustomType>) => {
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<HonoCustomType>): Promise<string | null> => {
return await utils.getSetting(c, CONSTANTS.DB_VERSION_KEY);
return await getSetting(c, CONSTANTS.DB_VERSION_KEY);
}
}
+2 -17
View File
@@ -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<OpenAIResponsesResponse>()
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
+10 -28
View File
@@ -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<void>
): TransformStream<Uint8Array, Uint8Array> => {
@@ -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<OpenAIResponse & Record<string, any>>();
const usage = normalizeUsage(resJson?.usage);
const usage = normalizeResponsesUsage(resJson?.usage);
if (usage) {
await saveUsage(usage);
}
+2 -17
View File
@@ -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<OpenAIResponsesResponse>()
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
+5 -1
View File
@@ -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;
@@ -95,4 +95,25 @@ export const handleStreamResponse = async (
}
}
export const checkoutResponsesUsageData = async (
saveUsage: (usage: Usage) => Promise<void>,
response: Response,
requestBody: any,
): Promise<void> => {
try {
const resJson = await response.clone().json<OpenAIResponsesResponse>()
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 }
-9
View File
@@ -106,12 +106,3 @@ export const findDeploymentMapping = (
return null;
}
export default {
getJsonObjectValue,
getJsonSetting,
getSetting,
saveSetting,
wildcardMatch,
findDeploymentMapping,
}