- 在 ChannelConfig 中添加 models 字段以支持模型映射 - 新增 ChannelFetchModelsEndpoint 用于获取上游模型列表 - 更新 normalizeChannelConfig 函数以处理新模型结构 - 修改相关的数据库操作以适应新的配置结构 - 引入 buildPrefixedTargetUrl 函数以简化 URL 构建 - 更新 utils 函数以支持模型映射查找
162 lines
4.4 KiB
TypeScript
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);
|
|
}
|