aicodeswitch 3.9.2 → 3.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/fs-database.js +44 -1
- package/dist/server/main.js +60 -3
- package/dist/server/proxy-server.js +74 -50
- package/dist/ui/assets/index-BqSYpNgU.js +511 -0
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
- package/dist/ui/assets/index-COkJEguF.js +0 -511
|
@@ -209,6 +209,8 @@ class FileSystemDatabaseManager {
|
|
|
209
209
|
yield this.loadAllData();
|
|
210
210
|
// 执行数据源类型迁移(在加载数据之后)
|
|
211
211
|
yield this.migrateSourceTypes();
|
|
212
|
+
// OpenAI base URL 迁移:将末尾 /v1 自动移除
|
|
213
|
+
yield this.migrateOpenAIBaseUrls();
|
|
212
214
|
// 确保默认配置
|
|
213
215
|
yield this.ensureDefaultConfig();
|
|
214
216
|
});
|
|
@@ -357,6 +359,41 @@ class FileSystemDatabaseManager {
|
|
|
357
359
|
console.log(`[TypeMigration] Migration completed. Migrated ${migratedCount} services.`);
|
|
358
360
|
});
|
|
359
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* 迁移 OpenAI base URL(在初始化时执行)
|
|
364
|
+
* 仅处理 sourceType=openai 且 apiUrl 末尾为 /v1 的服务
|
|
365
|
+
*/
|
|
366
|
+
migrateOpenAIBaseUrls() {
|
|
367
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
368
|
+
console.log('[OpenAIBaseUrlMigration] Checking for OpenAI base URL migration...');
|
|
369
|
+
let migratedCount = 0;
|
|
370
|
+
for (const vendor of this.vendors) {
|
|
371
|
+
if (!vendor.services)
|
|
372
|
+
continue;
|
|
373
|
+
for (const service of vendor.services) {
|
|
374
|
+
if (service.sourceType !== 'openai' || typeof service.apiUrl !== 'string') {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
const trimmedUrl = service.apiUrl.trim();
|
|
378
|
+
if (!/\/v1\/?$/i.test(trimmedUrl)) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
const migratedUrl = trimmedUrl.replace(/\/v1\/?$/i, '');
|
|
382
|
+
if (migratedUrl && migratedUrl !== service.apiUrl) {
|
|
383
|
+
console.log(`[OpenAIBaseUrlMigration] Migrated service "${service.name}": ${service.apiUrl} -> ${migratedUrl}`);
|
|
384
|
+
service.apiUrl = migratedUrl;
|
|
385
|
+
migratedCount++;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (migratedCount === 0) {
|
|
390
|
+
console.log('[OpenAIBaseUrlMigration] No migration needed');
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
yield this.saveVendors();
|
|
394
|
+
console.log(`[OpenAIBaseUrlMigration] Migration completed. Migrated ${migratedCount} services.`);
|
|
395
|
+
});
|
|
396
|
+
}
|
|
360
397
|
/**
|
|
361
398
|
* 迁移导入数据中的类型
|
|
362
399
|
* 用于导入功能,自动将旧类型转换为新类型
|
|
@@ -364,7 +401,13 @@ class FileSystemDatabaseManager {
|
|
|
364
401
|
migrateVendorsOnImport(vendors) {
|
|
365
402
|
return vendors.map(vendor => {
|
|
366
403
|
var _a;
|
|
367
|
-
return (Object.assign(Object.assign({}, vendor), { services: (_a = vendor.services) === null || _a === void 0 ? void 0 : _a.map(service =>
|
|
404
|
+
return (Object.assign(Object.assign({}, vendor), { services: (_a = vendor.services) === null || _a === void 0 ? void 0 : _a.map(service => {
|
|
405
|
+
const normalizedSourceType = service.sourceType ? (0, type_migration_1.normalizeSourceType)(service.sourceType) : undefined;
|
|
406
|
+
const normalizedApiUrl = normalizedSourceType === 'openai' && typeof service.apiUrl === 'string'
|
|
407
|
+
? service.apiUrl.trim().replace(/\/v1\/?$/i, '')
|
|
408
|
+
: service.apiUrl;
|
|
409
|
+
return Object.assign(Object.assign({}, service), { sourceType: normalizedSourceType, apiUrl: normalizedApiUrl });
|
|
410
|
+
}) }));
|
|
368
411
|
});
|
|
369
412
|
}
|
|
370
413
|
saveVendors() {
|
package/dist/server/main.js
CHANGED
|
@@ -104,6 +104,31 @@ const asyncHandler = (handler) => (req, res, next) => {
|
|
|
104
104
|
next(err);
|
|
105
105
|
});
|
|
106
106
|
};
|
|
107
|
+
const OPENAI_V1_SUFFIX_RE = /\/v1\/?$/i;
|
|
108
|
+
const validateOpenAIServiceBaseUrl = (service) => {
|
|
109
|
+
if ((service === null || service === void 0 ? void 0 : service.sourceType) !== 'openai') {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
if (typeof service.apiUrl !== 'string') {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
if (!OPENAI_V1_SUFFIX_RE.test(service.apiUrl.trim())) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
return 'OpenAI 数据源请填写不包含 /v1 的 base URL,例如:https://api.openai.com';
|
|
119
|
+
};
|
|
120
|
+
const validateOpenAIServiceBaseUrlsInVendorPayload = (vendorBody) => {
|
|
121
|
+
if (!Array.isArray(vendorBody === null || vendorBody === void 0 ? void 0 : vendorBody.services)) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
for (const service of vendorBody.services) {
|
|
125
|
+
const error = validateOpenAIServiceBaseUrl(service);
|
|
126
|
+
if (error) {
|
|
127
|
+
return error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
};
|
|
107
132
|
const writeClaudeConfig = (dbManager, enableAgentTeams, enableBypassPermissionsSupport) => __awaiter(void 0, void 0, void 0, function* () {
|
|
108
133
|
try {
|
|
109
134
|
const homeDir = os_1.default.homedir();
|
|
@@ -902,8 +927,22 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
902
927
|
}
|
|
903
928
|
});
|
|
904
929
|
app.get('/api/vendors', (_req, res) => res.json(dbManager.getVendors()));
|
|
905
|
-
app.post('/api/vendors', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
906
|
-
|
|
930
|
+
app.post('/api/vendors', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
931
|
+
const error = validateOpenAIServiceBaseUrlsInVendorPayload(req.body);
|
|
932
|
+
if (error) {
|
|
933
|
+
res.status(400).json({ error });
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
res.json(yield dbManager.createVendor(req.body));
|
|
937
|
+
})));
|
|
938
|
+
app.put('/api/vendors/:id', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
939
|
+
const error = validateOpenAIServiceBaseUrlsInVendorPayload(req.body);
|
|
940
|
+
if (error) {
|
|
941
|
+
res.status(400).json({ error });
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
res.json(yield dbManager.updateVendor(req.params.id, req.body));
|
|
945
|
+
})));
|
|
907
946
|
app.delete('/api/vendors/:id', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
908
947
|
try {
|
|
909
948
|
const result = yield dbManager.deleteVendor(req.params.id);
|
|
@@ -921,11 +960,29 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
921
960
|
});
|
|
922
961
|
app.post('/api/services', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
923
962
|
console.log('[创建服务] 请求数据:', JSON.stringify(req.body, null, 2));
|
|
963
|
+
const error = validateOpenAIServiceBaseUrl(req.body);
|
|
964
|
+
if (error) {
|
|
965
|
+
res.status(400).json({ error });
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
924
968
|
const result = yield dbManager.createAPIService(req.body);
|
|
925
969
|
console.log('[创建服务] 创建结果:', JSON.stringify(result, null, 2));
|
|
926
970
|
res.json(result);
|
|
927
971
|
})));
|
|
928
|
-
app.put('/api/services/:id', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
972
|
+
app.put('/api/services/:id', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
973
|
+
const existingService = dbManager.getAPIService(req.params.id);
|
|
974
|
+
if (!existingService) {
|
|
975
|
+
res.status(404).json({ error: '服务不存在' });
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
const mergedService = Object.assign(Object.assign({}, existingService), req.body);
|
|
979
|
+
const error = validateOpenAIServiceBaseUrl(mergedService);
|
|
980
|
+
if (error) {
|
|
981
|
+
res.status(400).json({ error });
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
res.json(yield dbManager.updateAPIService(req.params.id, req.body));
|
|
985
|
+
})));
|
|
929
986
|
app.delete('/api/services/:id', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
930
987
|
console.log('[删除服务] 请求 ID:', req.params.id);
|
|
931
988
|
try {
|
|
@@ -887,7 +887,8 @@ class ProxyServer {
|
|
|
887
887
|
return undefined;
|
|
888
888
|
const body = req.body;
|
|
889
889
|
const requestModel = body === null || body === void 0 ? void 0 : body.model;
|
|
890
|
-
const
|
|
890
|
+
const route = this.dbManager.getRoutes().find(r => r.id === routeId);
|
|
891
|
+
const contentType = forcedContentType || this.determineContentType(req, (route === null || route === void 0 ? void 0 : route.targetType) || 'claude-code', routeId);
|
|
891
892
|
// 高智商规则优先于 model-mapping,确保 !!/推断命中时不会被模型映射覆盖
|
|
892
893
|
if (contentType === 'high-iq') {
|
|
893
894
|
const highIqRules = enabledRules.filter(rule => rule.contentType === 'high-iq');
|
|
@@ -910,7 +911,32 @@ class ProxyServer {
|
|
|
910
911
|
return rule;
|
|
911
912
|
}
|
|
912
913
|
}
|
|
913
|
-
// 1.
|
|
914
|
+
// 1. 查找其他内容类型的规则
|
|
915
|
+
const contentTypeRules = enabledRules.filter(rule => rule.contentType === contentType);
|
|
916
|
+
// 过滤黑名单和token限制
|
|
917
|
+
for (const rule of contentTypeRules) {
|
|
918
|
+
const isBlacklisted = yield this.dbManager.isServiceBlacklisted(rule.targetServiceId, routeId, contentType);
|
|
919
|
+
if (isBlacklisted) {
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
// 检查并重置到期的规则
|
|
923
|
+
this.dbManager.checkAndResetRuleIfNeeded(rule.id);
|
|
924
|
+
this.dbManager.checkAndResetRequestCountIfNeeded(rule.id);
|
|
925
|
+
// 检查token限制(tokenLimit单位是k,需要乘以1000转换为实际token数)
|
|
926
|
+
if (rule.tokenLimit && rule.totalTokensUsed !== undefined && rule.totalTokensUsed >= rule.tokenLimit * 1000) {
|
|
927
|
+
continue; // 跳过超限规则
|
|
928
|
+
}
|
|
929
|
+
// 检查请求次数限制
|
|
930
|
+
if (rule.requestCountLimit && rule.totalRequestsUsed !== undefined && rule.totalRequestsUsed >= rule.requestCountLimit) {
|
|
931
|
+
continue; // 跳过超限规则
|
|
932
|
+
}
|
|
933
|
+
// 检查频率限制
|
|
934
|
+
if (this.isFrequencyLimitExceeded(rule)) {
|
|
935
|
+
continue; // 跳过达到频率限制的规则
|
|
936
|
+
}
|
|
937
|
+
return rule;
|
|
938
|
+
}
|
|
939
|
+
// 2. 然后查找 model-mapping 类型的规则
|
|
914
940
|
if (requestModel) {
|
|
915
941
|
const modelMappingRules = enabledRules.filter(rule => rule.contentType === 'model-mapping' &&
|
|
916
942
|
rule.replacedModel &&
|
|
@@ -939,31 +965,6 @@ class ProxyServer {
|
|
|
939
965
|
return rule;
|
|
940
966
|
}
|
|
941
967
|
}
|
|
942
|
-
// 2. 查找其他内容类型的规则
|
|
943
|
-
const contentTypeRules = enabledRules.filter(rule => rule.contentType === contentType);
|
|
944
|
-
// 过滤黑名单和token限制
|
|
945
|
-
for (const rule of contentTypeRules) {
|
|
946
|
-
const isBlacklisted = yield this.dbManager.isServiceBlacklisted(rule.targetServiceId, routeId, contentType);
|
|
947
|
-
if (isBlacklisted) {
|
|
948
|
-
continue;
|
|
949
|
-
}
|
|
950
|
-
// 检查并重置到期的规则
|
|
951
|
-
this.dbManager.checkAndResetRuleIfNeeded(rule.id);
|
|
952
|
-
this.dbManager.checkAndResetRequestCountIfNeeded(rule.id);
|
|
953
|
-
// 检查token限制(tokenLimit单位是k,需要乘以1000转换为实际token数)
|
|
954
|
-
if (rule.tokenLimit && rule.totalTokensUsed !== undefined && rule.totalTokensUsed >= rule.tokenLimit * 1000) {
|
|
955
|
-
continue; // 跳过超限规则
|
|
956
|
-
}
|
|
957
|
-
// 检查请求次数限制
|
|
958
|
-
if (rule.requestCountLimit && rule.totalRequestsUsed !== undefined && rule.totalRequestsUsed >= rule.requestCountLimit) {
|
|
959
|
-
continue; // 跳过超限规则
|
|
960
|
-
}
|
|
961
|
-
// 检查频率限制
|
|
962
|
-
if (this.isFrequencyLimitExceeded(rule)) {
|
|
963
|
-
continue; // 跳过达到频率限制的规则
|
|
964
|
-
}
|
|
965
|
-
return rule;
|
|
966
|
-
}
|
|
967
968
|
// 3. 最后返回 default 规则
|
|
968
969
|
const defaultRules = enabledRules.filter(rule => rule.contentType === 'default');
|
|
969
970
|
// 过滤黑名单和token限制
|
|
@@ -1003,7 +1004,8 @@ class ProxyServer {
|
|
|
1003
1004
|
const body = req.body;
|
|
1004
1005
|
const requestModel = body === null || body === void 0 ? void 0 : body.model;
|
|
1005
1006
|
const candidates = [];
|
|
1006
|
-
const
|
|
1007
|
+
const route = this.dbManager.getRoutes().find(r => r.id === routeId);
|
|
1008
|
+
const contentType = forcedContentType || this.determineContentType(req, (route === null || route === void 0 ? void 0 : route.targetType) || 'claude-code', routeId);
|
|
1007
1009
|
const prioritizeContentType = contentType === 'high-iq';
|
|
1008
1010
|
const modelMappingRules = requestModel
|
|
1009
1011
|
? enabledRules.filter(rule => rule.contentType === 'model-mapping' &&
|
|
@@ -1133,7 +1135,7 @@ class ProxyServer {
|
|
|
1133
1135
|
}
|
|
1134
1136
|
}
|
|
1135
1137
|
}
|
|
1136
|
-
determineContentType(req) {
|
|
1138
|
+
determineContentType(req, targetType, routeId) {
|
|
1137
1139
|
const body = req.body;
|
|
1138
1140
|
if (!body)
|
|
1139
1141
|
return 'default';
|
|
@@ -1145,8 +1147,10 @@ class ProxyServer {
|
|
|
1145
1147
|
if (explicitType) {
|
|
1146
1148
|
return explicitType;
|
|
1147
1149
|
}
|
|
1150
|
+
// 获取sessionId用于session级别的检测(如long-context)
|
|
1151
|
+
const sessionId = this.defaultExtractSessionId(req, targetType);
|
|
1148
1152
|
for (const detector of this.getContentTypeDetectors()) {
|
|
1149
|
-
if (detector.match(req, body)) {
|
|
1153
|
+
if (detector.match(req, body, sessionId, routeId)) {
|
|
1150
1154
|
return detector.type;
|
|
1151
1155
|
}
|
|
1152
1156
|
}
|
|
@@ -1158,17 +1162,17 @@ class ProxyServer {
|
|
|
1158
1162
|
type: 'image-understanding',
|
|
1159
1163
|
match: (_req, body) => this.containsImageContent(body.messages) || this.containsImageContent(body.input),
|
|
1160
1164
|
},
|
|
1161
|
-
{
|
|
1162
|
-
type: 'thinking',
|
|
1163
|
-
match: (_req, body) => this.hasThinkingSignal(body),
|
|
1164
|
-
},
|
|
1165
1165
|
{
|
|
1166
1166
|
type: 'high-iq',
|
|
1167
1167
|
match: (_req, body) => this.hasHighIqSignal(body),
|
|
1168
1168
|
},
|
|
1169
1169
|
{
|
|
1170
1170
|
type: 'long-context',
|
|
1171
|
-
match: (_req, body) => this.hasLongContextSignal(body),
|
|
1171
|
+
match: (_req, body, sessionId, routeId) => this.hasLongContextSignal(body, sessionId, routeId),
|
|
1172
|
+
},
|
|
1173
|
+
{
|
|
1174
|
+
type: 'thinking',
|
|
1175
|
+
match: (_req, body) => this.hasThinkingSignal(body),
|
|
1172
1176
|
},
|
|
1173
1177
|
{
|
|
1174
1178
|
type: 'background',
|
|
@@ -1574,8 +1578,8 @@ class ProxyServer {
|
|
|
1574
1578
|
];
|
|
1575
1579
|
return candidates.some((value) => value === true || value === 'background');
|
|
1576
1580
|
}
|
|
1577
|
-
hasLongContextSignal(body) {
|
|
1578
|
-
var _a, _b;
|
|
1581
|
+
hasLongContextSignal(body, sessionId, routeId) {
|
|
1582
|
+
var _a, _b, _c;
|
|
1579
1583
|
const explicit = [
|
|
1580
1584
|
body === null || body === void 0 ? void 0 : body.long_context,
|
|
1581
1585
|
body === null || body === void 0 ? void 0 : body.longContext,
|
|
@@ -1585,6 +1589,22 @@ class ProxyServer {
|
|
|
1585
1589
|
if (explicit.some((value) => value === true)) {
|
|
1586
1590
|
return true;
|
|
1587
1591
|
}
|
|
1592
|
+
// 检查session累积tokens
|
|
1593
|
+
if (sessionId && routeId) {
|
|
1594
|
+
const session = this.dbManager.getSession(sessionId);
|
|
1595
|
+
if (session && session.totalTokens > 0) {
|
|
1596
|
+
// 查找该route下的long-context规则,获取阈值配置
|
|
1597
|
+
const rules = this.getRulesByRouteId(routeId);
|
|
1598
|
+
const longContextRule = rules === null || rules === void 0 ? void 0 : rules.find(rule => rule.contentType === 'long-context' && !rule.isDisabled);
|
|
1599
|
+
// 默认阈值为1M tokens (1000k)
|
|
1600
|
+
const defaultThreshold = 1000; // 单位:k
|
|
1601
|
+
const threshold = (_c = longContextRule === null || longContextRule === void 0 ? void 0 : longContextRule.sessionTokenThreshold) !== null && _c !== void 0 ? _c : defaultThreshold;
|
|
1602
|
+
// 如果session累积tokens超过阈值,则认为是long-context
|
|
1603
|
+
if (session.totalTokens >= threshold * 1000) {
|
|
1604
|
+
return true;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1588
1608
|
const maxTokens = this.extractNumericField(body, [
|
|
1589
1609
|
'max_tokens',
|
|
1590
1610
|
'max_output_tokens',
|
|
@@ -1694,19 +1714,15 @@ class ProxyServer {
|
|
|
1694
1714
|
}
|
|
1695
1715
|
/**
|
|
1696
1716
|
* 构建 OpenAI Responses 类型的完整 URL
|
|
1697
|
-
* -
|
|
1698
|
-
* -
|
|
1699
|
-
* - 兼容请求路径本身已携带版本前缀(如 /v1/responses)场景
|
|
1717
|
+
* - 用户填写不含 /v1 的 baseUrl
|
|
1718
|
+
* - 服务端固定拼接 /v1 + 请求路径
|
|
1700
1719
|
*/
|
|
1701
1720
|
buildOpenAIResponsesUrl(baseUrl, mappedPath) {
|
|
1702
1721
|
const trimmedBase = baseUrl.trim().replace(/\/+$/, '');
|
|
1703
1722
|
const normalizedPath = mappedPath.startsWith('/') || mappedPath === '' ? mappedPath : `/${mappedPath}`;
|
|
1704
|
-
const
|
|
1705
|
-
const
|
|
1706
|
-
|
|
1707
|
-
return `${trimmedBase}${normalizedPath}`;
|
|
1708
|
-
}
|
|
1709
|
-
return `${trimmedBase}/v1${normalizedPath}`;
|
|
1723
|
+
const pathWithoutVersionPrefix = normalizedPath.replace(/^\/v\d+(?=\/|$)/i, '');
|
|
1724
|
+
const normalizedResponsesPath = pathWithoutVersionPrefix || '/responses';
|
|
1725
|
+
return `${trimmedBase}/v1${normalizedResponsesPath}`;
|
|
1710
1726
|
}
|
|
1711
1727
|
/**
|
|
1712
1728
|
* 构建 Gemini API 的完整 URL
|
|
@@ -2273,8 +2289,9 @@ class ProxyServer {
|
|
|
2273
2289
|
// Session 索引逻辑
|
|
2274
2290
|
const sessionId = this.defaultExtractSessionId(req, targetType);
|
|
2275
2291
|
if (sessionId) {
|
|
2276
|
-
|
|
2277
|
-
|
|
2292
|
+
// 正确计算当前请求的tokens:优先使用totalTokens,否则使用input+output
|
|
2293
|
+
const totalTokens = (usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.totalTokens) ||
|
|
2294
|
+
(((usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.inputTokens) || 0) + ((usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.outputTokens) || 0));
|
|
2278
2295
|
const sessionTitle = this.defaultExtractSessionTitle(req, sessionId);
|
|
2279
2296
|
const existingSession = this.dbManager.getSession(sessionId);
|
|
2280
2297
|
this.dbManager.upsertSession({
|
|
@@ -2298,7 +2315,8 @@ class ProxyServer {
|
|
|
2298
2315
|
}
|
|
2299
2316
|
// 更新规则的token使用量(只在成功请求时更新)
|
|
2300
2317
|
if (usageForLog && statusCode < 400) {
|
|
2301
|
-
const totalTokens =
|
|
2318
|
+
const totalTokens = usageForLog.totalTokens ||
|
|
2319
|
+
((usageForLog.inputTokens || 0) + (usageForLog.outputTokens || 0));
|
|
2302
2320
|
if (totalTokens > 0) {
|
|
2303
2321
|
this.dbManager.incrementRuleTokenUsage(rule.id, totalTokens);
|
|
2304
2322
|
// 获取更新后的规则数据并广播
|
|
@@ -2448,7 +2466,13 @@ class ProxyServer {
|
|
|
2448
2466
|
upstreamUrl = this.buildGeminiUrl(service.apiUrl, model, streamRequested);
|
|
2449
2467
|
}
|
|
2450
2468
|
else if (sourceType === 'openai') {
|
|
2451
|
-
|
|
2469
|
+
if (/\/v1\/?$/i.test(service.apiUrl.trim())) {
|
|
2470
|
+
const error = 'OpenAI 数据源请填写不包含 /v1 的 base URL,例如:https://api.openai.com';
|
|
2471
|
+
res.status(400).json({ error });
|
|
2472
|
+
yield finalizeLog(400, error);
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
// OpenAI Responses:固定拼接为 {baseUrl}/v1/*
|
|
2452
2476
|
upstreamUrl = this.buildOpenAIResponsesUrl(service.apiUrl, mappedPath);
|
|
2453
2477
|
}
|
|
2454
2478
|
else if (this.isChatType(sourceType) || this.isGeminiChatSource(sourceType)) {
|