Files
one-api-workers/src/utils.ts
T
Jeff Gu 84cc346ebd feat(channel): 添加模型映射和获取模型的 API
- 在 ChannelConfig 中添加 models 字段以支持模型映射
- 新增 ChannelFetchModelsEndpoint 用于获取上游模型列表
- 更新 normalizeChannelConfig 函数以处理新模型结构
- 修改相关的数据库操作以适应新的配置结构
- 引入 buildPrefixedTargetUrl 函数以简化 URL 构建
- 更新 utils 函数以支持模型映射查找
2026-04-09 11:56:38 +08:00

162 lines
4.4 KiB
TypeScript

import { Context } from "hono";
import { normalizeChannelConfig } from "./channel-config";
const getJsonObjectValue = <T = any>(
value: string | any
): T | null => {
if (value == undefined || value == null) {
return null;
}
if (typeof value === "object") {
return value as T;
}
if (typeof value !== "string") {
return null;
}
try {
return JSON.parse(value) as T;
} catch (e) {
console.error(`GetJsonValue: Failed to parse ${value}`, e);
}
return null;
}
export const getJsonSetting = async <T = any>(
c: Context<HonoCustomType>, key: string
): Promise<T | null> => {
const value = await getSetting(c, key);
if (!value) {
return null;
}
try {
return JSON.parse(value) as T;
} catch (e) {
console.error(`GetJsonSetting: Failed to parse ${key}`, e);
}
return null;
}
export const getSetting = async (
c: Context<HonoCustomType>, key: string
): Promise<string | null> => {
try {
const value = await c.env.DB.prepare(
`SELECT value FROM settings where key = ?`
).bind(key).first<string>("value");
return value;
} catch (error) {
console.error(`GetSetting: Failed to get ${key}`, error);
}
return null;
}
export const saveSetting = async (
c: Context<HonoCustomType>,
key: string, value: string
) => {
await c.env.DB.prepare(
`INSERT INTO settings (key, value) VALUES (?, ?)`
+ ` ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = datetime('now')`
).bind(key, value, value).run();
return true;
}
/**
* 通配符匹配函数
* 支持 * 匹配任意字符
* 例如: "claude-*" 匹配 "claude-opus-4", "*claude*" 匹配 "my-claude-model"
*/
export const wildcardMatch = (pattern: string, str: string): boolean => {
// 精确匹配优先
if (pattern === str) return true;
// 没有通配符则不匹配
if (!pattern.includes('*')) return false;
// 将通配符模式转换为正则表达式
const regexPattern = pattern
.split('*')
.map(s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
.join('.*');
const regex = new RegExp(`^${regexPattern}$`, 'i');
return regex.test(str);
}
/**
* 从 deployment_mapper 中查找匹配的部署名称
* 支持精确匹配和通配符匹配
* 返回 { pattern, deployment } 或 null
*/
export const findDeploymentMapping = (
deploymentMapper: Record<string, string> | undefined,
model: string
): { pattern: string; deployment: string } | null => {
if (!deploymentMapper) return null;
// 1. 精确匹配优先
if (deploymentMapper[model]) {
return { pattern: model, deployment: deploymentMapper[model] };
}
// 2. 通配符匹配
for (const pattern of Object.keys(deploymentMapper)) {
if (pattern.includes('*') && wildcardMatch(pattern, model)) {
return { pattern, deployment: deploymentMapper[pattern] };
}
}
return null;
}
export const getChannelModels = (
config: Partial<Pick<ChannelConfig, "models" | "supported_models" | "deployment_mapper">>
): ChannelModelMapping[] => {
return normalizeChannelConfig(config as Partial<ChannelConfig>).models;
}
export const findChannelModelMapping = (
config: Partial<Pick<ChannelConfig, "models" | "supported_models" | "deployment_mapper">>,
model: string
): ChannelModelMapping | null => {
const channelModels = getChannelModels(config);
for (const channelModel of channelModels) {
if (channelModel.name === model) {
return channelModel;
}
}
for (const channelModel of channelModels) {
if (channelModel.name.includes('*') && wildcardMatch(channelModel.name, model)) {
return channelModel;
}
}
return null;
}
export const findSupportedModel = (
supportedModels: string[] | undefined,
model: string
): string | null => {
if (!supportedModels || supportedModels.length === 0) return null;
if (supportedModels.includes(model)) {
return model;
}
for (const pattern of supportedModels) {
if (pattern.includes('*') && wildcardMatch(pattern, model)) {
return pattern;
}
}
return null;
}
export const getSupportedModels = (
config: Pick<ChannelConfig, "models" | "supported_models" | "deployment_mapper">
): string[] => {
return getChannelModels(config).map((channelModel) => channelModel.name);
}