aicodeswitch 3.9.3 → 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.
|
@@ -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 {
|
|
@@ -1714,19 +1714,15 @@ class ProxyServer {
|
|
|
1714
1714
|
}
|
|
1715
1715
|
/**
|
|
1716
1716
|
* 构建 OpenAI Responses 类型的完整 URL
|
|
1717
|
-
* -
|
|
1718
|
-
* -
|
|
1719
|
-
* - 兼容请求路径本身已携带版本前缀(如 /v1/responses)场景
|
|
1717
|
+
* - 用户填写不含 /v1 的 baseUrl
|
|
1718
|
+
* - 服务端固定拼接 /v1 + 请求路径
|
|
1720
1719
|
*/
|
|
1721
1720
|
buildOpenAIResponsesUrl(baseUrl, mappedPath) {
|
|
1722
1721
|
const trimmedBase = baseUrl.trim().replace(/\/+$/, '');
|
|
1723
1722
|
const normalizedPath = mappedPath.startsWith('/') || mappedPath === '' ? mappedPath : `/${mappedPath}`;
|
|
1724
|
-
const
|
|
1725
|
-
const
|
|
1726
|
-
|
|
1727
|
-
return `${trimmedBase}${normalizedPath}`;
|
|
1728
|
-
}
|
|
1729
|
-
return `${trimmedBase}/v1${normalizedPath}`;
|
|
1723
|
+
const pathWithoutVersionPrefix = normalizedPath.replace(/^\/v\d+(?=\/|$)/i, '');
|
|
1724
|
+
const normalizedResponsesPath = pathWithoutVersionPrefix || '/responses';
|
|
1725
|
+
return `${trimmedBase}/v1${normalizedResponsesPath}`;
|
|
1730
1726
|
}
|
|
1731
1727
|
/**
|
|
1732
1728
|
* 构建 Gemini API 的完整 URL
|
|
@@ -2470,7 +2466,13 @@ class ProxyServer {
|
|
|
2470
2466
|
upstreamUrl = this.buildGeminiUrl(service.apiUrl, model, streamRequested);
|
|
2471
2467
|
}
|
|
2472
2468
|
else if (sourceType === 'openai') {
|
|
2473
|
-
|
|
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/*
|
|
2474
2476
|
upstreamUrl = this.buildOpenAIResponsesUrl(service.apiUrl, mappedPath);
|
|
2475
2477
|
}
|
|
2476
2478
|
else if (this.isChatType(sourceType) || this.isGeminiChatSource(sourceType)) {
|