aicodeswitch 5.2.3 → 5.2.5
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/conversions/detector.js +13 -0
- package/dist/server/conversions/index.js +2 -1
- package/dist/server/conversions/pairs/responses-responses/request.js +7 -8
- package/dist/server/fs-database.js +161 -0
- package/dist/server/main.js +13 -0
- package/dist/server/proxy-server.js +14 -10
- package/dist/ui/assets/index-BTjE2wK0.js +821 -0
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
- package/dist/ui/assets/index-Cws89pD2.js +0 -828
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.detectRequestFormat = detectRequestFormat;
|
|
4
|
+
exports.isOfficialOpenAiApi = isOfficialOpenAiApi;
|
|
4
5
|
exports.sourceTypeToFormat = sourceTypeToFormat;
|
|
5
6
|
/**
|
|
6
7
|
* Detect the format of the incoming request based on path and body structure.
|
|
@@ -48,6 +49,18 @@ function detectRequestFormat(path, body) {
|
|
|
48
49
|
// Default to completions
|
|
49
50
|
return 'completions';
|
|
50
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* 判断上游 URL 是否为 OpenAI 官方(含 Azure OpenAI)端点。
|
|
54
|
+
*
|
|
55
|
+
* 官方端点完整支持 Responses API 的私有扩展(custom 自定义工具、
|
|
56
|
+
* tool_search/web_search 等内置工具、reasoning.effort、text.verbosity 等),
|
|
57
|
+
* 因此 responses→responses 直连时无需降级兼容。其余第三方 Responses 提供商
|
|
58
|
+
* (火山方舟/豆包等)不支持这些扩展,需要走 downgradeResponsesRequest。
|
|
59
|
+
*/
|
|
60
|
+
function isOfficialOpenAiApi(apiUrl) {
|
|
61
|
+
const u = (apiUrl || '').toLowerCase();
|
|
62
|
+
return u.includes('api.openai.com') || u.includes('.openai.azure.com');
|
|
63
|
+
}
|
|
51
64
|
/**
|
|
52
65
|
* Determine the upstream format from a SourceType string.
|
|
53
66
|
* Maps the legacy SourceType values to the new Format type.
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* is driven by the ReasoningConfig passed through TransformRequestOptions.
|
|
16
16
|
*/
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.processCompactResponse = exports.prepareCompactRequest = exports.buildCompactedResponse = exports.extractSummaryFromResponse = exports.buildCompactUpstreamRequest = exports.buildCompactionPrompt = exports.COMPACTION_SYSTEM_PROMPT = exports.isCodexCompactRequest = exports.isLastClaudeMessageCompact = exports.isClaudeCompactRequest = exports.extractMessageContent = exports.extractConversationText = exports.sanitizeRequestBody = exports.getServerToolSupport = exports.getReasoningConfig = exports.sourceTypeToFormat = exports.detectRequestFormat = void 0;
|
|
18
|
+
exports.processCompactResponse = exports.prepareCompactRequest = exports.buildCompactedResponse = exports.extractSummaryFromResponse = exports.buildCompactUpstreamRequest = exports.buildCompactionPrompt = exports.COMPACTION_SYSTEM_PROMPT = exports.isCodexCompactRequest = exports.isLastClaudeMessageCompact = exports.isClaudeCompactRequest = exports.extractMessageContent = exports.extractConversationText = exports.sanitizeRequestBody = exports.getServerToolSupport = exports.getReasoningConfig = exports.isOfficialOpenAiApi = exports.sourceTypeToFormat = exports.detectRequestFormat = void 0;
|
|
19
19
|
exports.transformRequest = transformRequest;
|
|
20
20
|
exports.transformResponse = transformResponse;
|
|
21
21
|
exports.createStreamConverter = createStreamConverter;
|
|
@@ -23,6 +23,7 @@ exports.buildTargetBody = buildTargetBody;
|
|
|
23
23
|
var detector_js_1 = require("./detector.js");
|
|
24
24
|
Object.defineProperty(exports, "detectRequestFormat", { enumerable: true, get: function () { return detector_js_1.detectRequestFormat; } });
|
|
25
25
|
Object.defineProperty(exports, "sourceTypeToFormat", { enumerable: true, get: function () { return detector_js_1.sourceTypeToFormat; } });
|
|
26
|
+
Object.defineProperty(exports, "isOfficialOpenAiApi", { enumerable: true, get: function () { return detector_js_1.isOfficialOpenAiApi; } });
|
|
26
27
|
var providers_js_1 = require("./thinking/providers.js");
|
|
27
28
|
Object.defineProperty(exports, "getReasoningConfig", { enumerable: true, get: function () { return providers_js_1.getReasoningConfig; } });
|
|
28
29
|
const providers_js_2 = require("./thinking/providers.js");
|
|
@@ -8,13 +8,12 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.downgradeResponsesRequest = downgradeResponsesRequest;
|
|
10
10
|
/**
|
|
11
|
-
* Responses API
|
|
12
|
-
* OpenAI 私有扩展(custom、
|
|
13
|
-
*
|
|
11
|
+
* 第三方 Responses API 提供商仅支持 function 类型工具。
|
|
12
|
+
* OpenAI 私有扩展(apply_patch 的 custom、MCP 的 namespace、
|
|
13
|
+
* tool_search、web_search、file_search、code_interpreter 等)一律剥离,
|
|
14
|
+
* 直接转发会导致 400 `unknown tool type` 错误。
|
|
15
|
+
* 采用 function 白名单而非黑名单,避免新增私有类型时遗漏。
|
|
14
16
|
*/
|
|
15
|
-
const NON_STANDARD_TOOL_TYPES = new Set([
|
|
16
|
-
'custom', 'tool_search', 'web_search', 'file_search', 'code_interpreter',
|
|
17
|
-
]);
|
|
18
17
|
/**
|
|
19
18
|
* Responses API 降级兼容时需移除的顶层字段集合。
|
|
20
19
|
* 这些字段非所有 Responses API 提供商都支持,开启降级兼容时会被移除以避免 400 错误。
|
|
@@ -67,9 +66,9 @@ function downgradeResponsesRequest(body) {
|
|
|
67
66
|
sanitized = Object.assign({}, sanitized);
|
|
68
67
|
return sanitized;
|
|
69
68
|
};
|
|
70
|
-
// 1.
|
|
69
|
+
// 1. 仅保留 function 类型工具,剥离 custom/namespace/tool_search/web_search 等私有类型
|
|
71
70
|
if (Array.isArray(body.tools)) {
|
|
72
|
-
const filteredTools = body.tools.filter((t) =>
|
|
71
|
+
const filteredTools = body.tools.filter((t) => t && t.type === 'function');
|
|
73
72
|
if (filteredTools.length !== body.tools.length) {
|
|
74
73
|
sanitized = Object.assign(Object.assign({}, sanitized), { tools: filteredTools });
|
|
75
74
|
}
|
|
@@ -3048,6 +3048,167 @@ class FileSystemDatabaseManager {
|
|
|
3048
3048
|
yield this.saveSessions();
|
|
3049
3049
|
});
|
|
3050
3050
|
}
|
|
3051
|
+
/**
|
|
3052
|
+
* 批量清理过期会话
|
|
3053
|
+
* 以最后请求时间为基准,清理 lastRequestAt 早于 beforeTimestamp 的会话。
|
|
3054
|
+
* @param beforeTimestamp 时间阈值(毫秒),最后请求时间严格早于此值的会话将被处理
|
|
3055
|
+
* @param options.onlyLogs 仅清空关联日志,保留会话本身
|
|
3056
|
+
* @returns 受影响会话数 / 删除的日志数
|
|
3057
|
+
*/
|
|
3058
|
+
cleanupSessionsByAge(beforeTimestamp_1) {
|
|
3059
|
+
return __awaiter(this, arguments, void 0, function* (beforeTimestamp, options = {}) {
|
|
3060
|
+
const targetSessions = this.sessions.filter(s => s.lastRequestAt < beforeTimestamp);
|
|
3061
|
+
if (targetSessions.length === 0) {
|
|
3062
|
+
return { sessionsAffected: 0, logsDeleted: 0 };
|
|
3063
|
+
}
|
|
3064
|
+
const targetIds = new Set(targetSessions.map(s => s.id));
|
|
3065
|
+
const { logsDeleted } = yield this.deleteLogsBySessionIds(targetIds);
|
|
3066
|
+
if (!options.onlyLogs) {
|
|
3067
|
+
this.sessions = this.sessions.filter(s => !targetIds.has(s.id));
|
|
3068
|
+
yield this.saveSessions();
|
|
3069
|
+
}
|
|
3070
|
+
return { sessionsAffected: targetSessions.length, logsDeleted };
|
|
3071
|
+
});
|
|
3072
|
+
}
|
|
3073
|
+
/**
|
|
3074
|
+
* 删除指定会话集合关联的所有日志条目,并维护分片文件与索引的一致性。
|
|
3075
|
+
* 多个会话可能共享同一分片,按分片重写并修正其余会话的 index 引用。
|
|
3076
|
+
* 与 addLog 共享分片写入锁(shardWriteLocks),避免并发写入竞争。
|
|
3077
|
+
*/
|
|
3078
|
+
deleteLogsBySessionIds(sessionIds) {
|
|
3079
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
3080
|
+
if (sessionIds.size === 0)
|
|
3081
|
+
return { logsDeleted: 0 };
|
|
3082
|
+
// 1. 按分片文件收集待删除的 index(基于内存索引快照)
|
|
3083
|
+
const shardDeletes = new Map();
|
|
3084
|
+
for (const sid of sessionIds) {
|
|
3085
|
+
const refs = this.sessionLogIndex.get(sid);
|
|
3086
|
+
if (!refs)
|
|
3087
|
+
continue;
|
|
3088
|
+
for (const ref of refs) {
|
|
3089
|
+
let set = shardDeletes.get(ref.filename);
|
|
3090
|
+
if (!set) {
|
|
3091
|
+
set = new Set();
|
|
3092
|
+
shardDeletes.set(ref.filename, set);
|
|
3093
|
+
}
|
|
3094
|
+
set.add(ref.index);
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
let logsDeleted = 0;
|
|
3098
|
+
// 2. 逐分片重写(在分片锁内执行,与该分片的 addLog 串行)
|
|
3099
|
+
for (const [filename, deleteSet] of shardDeletes) {
|
|
3100
|
+
let shardRemoved = 0;
|
|
3101
|
+
const prevLock = this.shardWriteLocks.get(filename) || Promise.resolve();
|
|
3102
|
+
const task = prevLock.then(() => __awaiter(this, void 0, void 0, function* () {
|
|
3103
|
+
const shardLogs = yield this.loadLogShard(filename);
|
|
3104
|
+
if (shardLogs.length === 0)
|
|
3105
|
+
return;
|
|
3106
|
+
// 构建 oldIndex -> newIndex 映射(仅保留项)
|
|
3107
|
+
const newIndexMap = new Map();
|
|
3108
|
+
const newLogs = [];
|
|
3109
|
+
let removed = 0;
|
|
3110
|
+
for (let i = 0; i < shardLogs.length; i++) {
|
|
3111
|
+
if (deleteSet.has(i)) {
|
|
3112
|
+
removed++;
|
|
3113
|
+
continue;
|
|
3114
|
+
}
|
|
3115
|
+
newIndexMap.set(i, newLogs.length);
|
|
3116
|
+
newLogs.push(shardLogs[i]);
|
|
3117
|
+
}
|
|
3118
|
+
if (removed === 0)
|
|
3119
|
+
return;
|
|
3120
|
+
shardRemoved = removed;
|
|
3121
|
+
// 重写或删除分片文件
|
|
3122
|
+
if (newLogs.length === 0) {
|
|
3123
|
+
try {
|
|
3124
|
+
yield promises_1.default.unlink(path_1.default.join(this.logsDir, filename));
|
|
3125
|
+
}
|
|
3126
|
+
catch (_a) {
|
|
3127
|
+
// 文件可能已被其它清理流程删除,忽略
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
else {
|
|
3131
|
+
yield this.saveLogShard(filename, newLogs);
|
|
3132
|
+
}
|
|
3133
|
+
// 更新分片索引
|
|
3134
|
+
const shardIndex = this.logShardsIndex.find(s => s.filename === filename);
|
|
3135
|
+
if (shardIndex) {
|
|
3136
|
+
if (newLogs.length === 0) {
|
|
3137
|
+
this.logShardsIndex = this.logShardsIndex.filter(s => s.filename !== filename);
|
|
3138
|
+
}
|
|
3139
|
+
else {
|
|
3140
|
+
shardIndex.count = newLogs.length;
|
|
3141
|
+
let minTs = Infinity;
|
|
3142
|
+
let maxTs = -Infinity;
|
|
3143
|
+
for (const l of newLogs) {
|
|
3144
|
+
if (l.timestamp < minTs)
|
|
3145
|
+
minTs = l.timestamp;
|
|
3146
|
+
if (l.timestamp > maxTs)
|
|
3147
|
+
maxTs = l.timestamp;
|
|
3148
|
+
}
|
|
3149
|
+
shardIndex.startTime = minTs;
|
|
3150
|
+
shardIndex.endTime = maxTs;
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
// 修正引用了该分片的非目标会话的 refs(index 偏移)
|
|
3154
|
+
for (const [sid, refs] of this.sessionLogIndex) {
|
|
3155
|
+
if (sessionIds.has(sid))
|
|
3156
|
+
continue;
|
|
3157
|
+
let touched = false;
|
|
3158
|
+
const updated = [];
|
|
3159
|
+
for (const ref of refs) {
|
|
3160
|
+
if (ref.filename !== filename) {
|
|
3161
|
+
updated.push(ref);
|
|
3162
|
+
continue;
|
|
3163
|
+
}
|
|
3164
|
+
if (deleteSet.has(ref.index)) {
|
|
3165
|
+
touched = true;
|
|
3166
|
+
continue;
|
|
3167
|
+
}
|
|
3168
|
+
const ni = newIndexMap.get(ref.index);
|
|
3169
|
+
if (ni !== undefined) {
|
|
3170
|
+
if (ni !== ref.index)
|
|
3171
|
+
touched = true;
|
|
3172
|
+
updated.push(Object.assign(Object.assign({}, ref), { index: ni }));
|
|
3173
|
+
}
|
|
3174
|
+
else {
|
|
3175
|
+
touched = true;
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
if (touched) {
|
|
3179
|
+
if (updated.length === 0) {
|
|
3180
|
+
this.sessionLogIndex.delete(sid);
|
|
3181
|
+
}
|
|
3182
|
+
else {
|
|
3183
|
+
this.sessionLogIndex.set(sid, updated);
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
}));
|
|
3188
|
+
this.shardWriteLocks.set(filename, task);
|
|
3189
|
+
try {
|
|
3190
|
+
yield task;
|
|
3191
|
+
logsDeleted += shardRemoved;
|
|
3192
|
+
}
|
|
3193
|
+
catch (err) {
|
|
3194
|
+
console.error(`[Database] Failed to cleanup shard ${filename}:`, err);
|
|
3195
|
+
}
|
|
3196
|
+
finally {
|
|
3197
|
+
if (this.shardWriteLocks.get(filename) === task) {
|
|
3198
|
+
this.shardWriteLocks.delete(filename);
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
// 3. 清除目标会话的索引条目
|
|
3203
|
+
for (const sid of sessionIds) {
|
|
3204
|
+
this.sessionLogIndex.delete(sid);
|
|
3205
|
+
}
|
|
3206
|
+
yield this.saveLogsIndex();
|
|
3207
|
+
yield this.saveSessionLogIndexNow();
|
|
3208
|
+
this.logsCountCache = null;
|
|
3209
|
+
return { logsDeleted };
|
|
3210
|
+
});
|
|
3211
|
+
}
|
|
3051
3212
|
// 新增方法:获取单个 session
|
|
3052
3213
|
getSession(id) {
|
|
3053
3214
|
return this.sessions.find(s => s.id === id) || null;
|
package/dist/server/main.js
CHANGED
|
@@ -2185,6 +2185,19 @@ ${instruction}
|
|
|
2185
2185
|
dbManager.clearSessions();
|
|
2186
2186
|
res.json(true);
|
|
2187
2187
|
})));
|
|
2188
|
+
// 清理过期会话(基于最后请求时间),可选择仅清空关联日志而保留会话本身
|
|
2189
|
+
app.post('/api/sessions/cleanup', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
2190
|
+
var _a, _b;
|
|
2191
|
+
const beforeDays = Number((_a = req.body) === null || _a === void 0 ? void 0 : _a.beforeDays);
|
|
2192
|
+
const onlyLogs = Boolean((_b = req.body) === null || _b === void 0 ? void 0 : _b.onlyLogs);
|
|
2193
|
+
if (!Number.isFinite(beforeDays) || !Number.isInteger(beforeDays) || beforeDays < 1 || beforeDays > 15) {
|
|
2194
|
+
res.status(400).json({ error: 'beforeDays 必须为 1-15 之间的整数' });
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
2197
|
+
const beforeTimestamp = Date.now() - beforeDays * 24 * 60 * 60 * 1000;
|
|
2198
|
+
const result = yield dbManager.cleanupSessionsByAge(beforeTimestamp, { onlyLogs });
|
|
2199
|
+
res.json(result);
|
|
2200
|
+
})));
|
|
2188
2201
|
// ─── Session Route Binding API ───
|
|
2189
2202
|
app.put('/api/sessions/:id/bind-route', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
2190
2203
|
const sessionId = req.params.id;
|
|
@@ -373,7 +373,7 @@ class ProxyServer {
|
|
|
373
373
|
if (apiPath === '/v1/models') {
|
|
374
374
|
// 鉴权
|
|
375
375
|
const apiKeyValue = this.extractApiKey(req);
|
|
376
|
-
if (apiKeyValue === null || apiKeyValue === void 0 ? void 0 : apiKeyValue.startsWith('sk_')) {
|
|
376
|
+
if ((apiKeyValue === null || apiKeyValue === void 0 ? void 0 : apiKeyValue.startsWith('sk_')) && (0, auth_1.isAuthEnabled)()) {
|
|
377
377
|
// AccessKey 鉴权
|
|
378
378
|
if (!this.accessKeyModule) {
|
|
379
379
|
res.status(401).json({ error: { type: 'authentication_error', code: 'INVALID_API_KEY', message: 'AccessKey 功能未启用' } });
|
|
@@ -399,7 +399,7 @@ class ProxyServer {
|
|
|
399
399
|
// 检查是否为 AccessKey 请求
|
|
400
400
|
const apiKeyValue = this.extractApiKey(req);
|
|
401
401
|
let accessKeyCtx = null;
|
|
402
|
-
if ((apiKeyValue === null || apiKeyValue === void 0 ? void 0 : apiKeyValue.startsWith('sk_')) && this.accessKeyModule) {
|
|
402
|
+
if ((apiKeyValue === null || apiKeyValue === void 0 ? void 0 : apiKeyValue.startsWith('sk_')) && this.accessKeyModule && (0, auth_1.isAuthEnabled)()) {
|
|
403
403
|
const result = this.accessKeyModule.keyResolver.resolve(apiKeyValue);
|
|
404
404
|
if (!result || 'error' in result) {
|
|
405
405
|
const err = result ? result.error : { type: 'authentication_error', code: 'INVALID_API_KEY', message: '无效的 API Key', httpStatus: 401 };
|
|
@@ -484,7 +484,7 @@ class ProxyServer {
|
|
|
484
484
|
}
|
|
485
485
|
// AUTH 鉴权检查
|
|
486
486
|
const apiKeyValue = this.extractApiKey(req);
|
|
487
|
-
if ((apiKeyValue === null || apiKeyValue === void 0 ? void 0 : apiKeyValue.startsWith('sk_')) && this.accessKeyModule) {
|
|
487
|
+
if ((apiKeyValue === null || apiKeyValue === void 0 ? void 0 : apiKeyValue.startsWith('sk_')) && this.accessKeyModule && (0, auth_1.isAuthEnabled)()) {
|
|
488
488
|
const result = this.accessKeyModule.keyResolver.resolve(apiKeyValue);
|
|
489
489
|
if (!result || 'error' in result) {
|
|
490
490
|
const err = result ? result.error : { type: 'authentication_error', code: 'INVALID_API_KEY', message: '无效的 API Key', httpStatus: 401 };
|
|
@@ -803,7 +803,7 @@ class ProxyServer {
|
|
|
803
803
|
try {
|
|
804
804
|
// 检查 API Key 验证(支持 AccessKey 和全局 apiKey)
|
|
805
805
|
const apiKeyValue = this.extractApiKey(req);
|
|
806
|
-
if ((apiKeyValue === null || apiKeyValue === void 0 ? void 0 : apiKeyValue.startsWith('sk_')) && this.accessKeyModule) {
|
|
806
|
+
if ((apiKeyValue === null || apiKeyValue === void 0 ? void 0 : apiKeyValue.startsWith('sk_')) && this.accessKeyModule && (0, auth_1.isAuthEnabled)()) {
|
|
807
807
|
// AccessKey 鉴权
|
|
808
808
|
const result = this.accessKeyModule.keyResolver.resolve(apiKeyValue);
|
|
809
809
|
if (!result || 'error' in result) {
|
|
@@ -3248,10 +3248,10 @@ class ProxyServer {
|
|
|
3248
3248
|
* @param targetModel 目标模型名称(可选)
|
|
3249
3249
|
* @returns 转换后往服务商API接口的数据
|
|
3250
3250
|
*/
|
|
3251
|
-
transformRequestToUpstream(tool, source, payloadData, targetModel, providerConfig, serverToolConfig) {
|
|
3251
|
+
transformRequestToUpstream(tool, source, payloadData, targetModel, providerConfig, serverToolConfig, sanitizeBody) {
|
|
3252
3252
|
const clientFormat = tool === 'codex' ? 'responses' : 'claude';
|
|
3253
3253
|
const upstreamFormat = (0, index_1.sourceTypeToFormat)(source);
|
|
3254
|
-
const result = (0, index_1.transformRequest)({ fromFormat: clientFormat, toFormat: upstreamFormat, body: payloadData, providerConfig, serverToolConfig });
|
|
3254
|
+
const result = (0, index_1.transformRequest)({ fromFormat: clientFormat, toFormat: upstreamFormat, body: payloadData, providerConfig, serverToolConfig, sanitizeBody });
|
|
3255
3255
|
const body = result.body;
|
|
3256
3256
|
// 模型覆盖:OpenAI 模型族保持原样,其余覆盖为 targetModel
|
|
3257
3257
|
if (targetModel) {
|
|
@@ -3878,7 +3878,9 @@ class ProxyServer {
|
|
|
3878
3878
|
const effectiveModel = rule.targetModel || (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model);
|
|
3879
3879
|
const providerConfig = (0, index_1.getReasoningConfig)(service.name || '', effectiveApiUrl || '', effectiveModel || '');
|
|
3880
3880
|
const serverToolConfig = (0, index_1.getServerToolSupport)(service.name || '', effectiveApiUrl || '');
|
|
3881
|
-
|
|
3881
|
+
// responses→responses 直连非 OpenAI 官方端点时,需降级兼容(剥离 custom/namespace 等私有工具与非标准字段)
|
|
3882
|
+
const sanitizeBody = clientFormat === 'responses' && (0, index_1.sourceTypeToFormat)(sourceType) === 'responses' && !(0, index_1.isOfficialOpenAiApi)(effectiveApiUrl || '');
|
|
3883
|
+
const transformedRequestBody = this.transformRequestToUpstream(targetType, sourceType, payloadForTransform, rule.targetModel, providerConfig, serverToolConfig, sanitizeBody);
|
|
3882
3884
|
requestBody = (_b = transformedRequestBody !== null && transformedRequestBody !== void 0 ? transformedRequestBody : this.cloneRequestBody(originalToolRequestBody)) !== null && _b !== void 0 ? _b : {};
|
|
3883
3885
|
// 对最终即将发送到上游的 Claude compact 请求再做一次兜底清理,
|
|
3884
3886
|
// 避免中间转换/覆盖步骤重新引入未配对的 tool_use。
|
|
@@ -4737,7 +4739,9 @@ class ProxyServer {
|
|
|
4737
4739
|
const effectiveModel = rule.targetModel || (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model);
|
|
4738
4740
|
const providerConfig = (0, index_1.getReasoningConfig)(service.name || '', effectiveApiUrl || '', effectiveModel || '');
|
|
4739
4741
|
const serverToolConfig = (0, index_1.getServerToolSupport)(service.name || '', effectiveApiUrl || '');
|
|
4740
|
-
|
|
4742
|
+
// responses→responses 直连非 OpenAI 官方端点时,需降级兼容(剥离 custom/namespace 等私有工具与非标准字段)
|
|
4743
|
+
const sanitizeBody = clientFormat === 'responses' && (0, index_1.sourceTypeToFormat)(sourceType) === 'responses' && !(0, index_1.isOfficialOpenAiApi)(effectiveApiUrl || '');
|
|
4744
|
+
const transformedRequestBody = this.transformRequestByFormat(clientFormat, sourceType, payloadForTransform, rule.targetModel, providerConfig, serverToolConfig, sanitizeBody);
|
|
4741
4745
|
requestBody = (_a = transformedRequestBody !== null && transformedRequestBody !== void 0 ? transformedRequestBody : this.cloneRequestBody(requestBody)) !== null && _a !== void 0 ? _a : {};
|
|
4742
4746
|
// Compact final sanitize
|
|
4743
4747
|
if (rule.contentType === 'compact' && clientFormat === 'claude' && Array.isArray(requestBody === null || requestBody === void 0 ? void 0 : requestBody.messages)) {
|
|
@@ -4992,9 +4996,9 @@ class ProxyServer {
|
|
|
4992
4996
|
/**
|
|
4993
4997
|
* 使用显式 clientFormat 进行请求转换(取代 tool → format 的硬编码映射)
|
|
4994
4998
|
*/
|
|
4995
|
-
transformRequestByFormat(clientFormat, source, payloadData, targetModel, providerConfig, serverToolConfig) {
|
|
4999
|
+
transformRequestByFormat(clientFormat, source, payloadData, targetModel, providerConfig, serverToolConfig, sanitizeBody) {
|
|
4996
5000
|
const upstreamFormat = (0, index_1.sourceTypeToFormat)(source);
|
|
4997
|
-
const result = (0, index_1.transformRequest)({ fromFormat: clientFormat, toFormat: upstreamFormat, body: payloadData, providerConfig, serverToolConfig });
|
|
5001
|
+
const result = (0, index_1.transformRequest)({ fromFormat: clientFormat, toFormat: upstreamFormat, body: payloadData, providerConfig, serverToolConfig, sanitizeBody });
|
|
4998
5002
|
const body = result.body;
|
|
4999
5003
|
if (targetModel) {
|
|
5000
5004
|
const isOpenAIModel = /^gpt-|o[123]/i.test(targetModel);
|