aicodeswitch 3.9.3 → 4.0.0
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/README.md +37 -36
- package/UPGRADE.md +5 -3
- package/bin/restore.js +126 -22
- package/bin/start.js +29 -41
- package/bin/stop.js +3 -3
- package/bin/utils/config-helpers.js +198 -0
- package/dist/server/config-managed-fields.js +69 -0
- package/dist/server/config-merge.js +260 -0
- package/dist/server/database-factory.js +11 -181
- package/dist/server/fs-database.js +211 -31
- package/dist/server/main.js +380 -241
- package/dist/server/original-config-reader.js +154 -88
- package/dist/server/proxy-server.js +993 -385
- package/dist/server/rules-status-service.js +285 -54
- package/dist/server/transformers/chunk-collector.js +26 -4
- package/dist/server/transformers/streaming.js +2334 -280
- package/dist/server/transformers/transformers.js +1765 -0
- package/dist/ui/assets/index-GQBwe1Rm.js +514 -0
- package/dist/ui/index.html +1 -1
- package/package.json +4 -8
- package/schema/claude.schema.md +15 -14
- package/schema/deepseek-chat.schema.md +799 -0
- package/schema/{openai.schema.md → openai-chat-completions.schema.md} +9 -1083
- package/schema/openai-responses.schema.md +226196 -0
- package/schema/stream.md +2592 -0
- package/dist/server/database.js +0 -1609
- package/dist/server/migrate-to-fs.js +0 -353
- package/dist/server/transformers/claude-openai.js +0 -868
- package/dist/server/transformers/gemini.js +0 -625
- package/dist/ui/assets/index-DNtgPQMm.js +0 -511
|
@@ -1,868 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.normalizeOpenAIStreamEvent = exports.transformResponsesToChatCompletions = exports.transformChatCompletionsToResponses = exports.extractTokenUsageFromClaudeUsage = exports.extractTokenUsageFromOpenAIUsage = exports.transformClaudeResponseToOpenAIChat = exports.transformOpenAIChatResponseToClaude = exports.transformClaudeRequestToOpenAIChat = exports.mapClaudeStopReasonToOpenAI = exports.mapStopReason = exports.convertOpenAIUsageToClaude = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* 将 Claude 图像 content block 转换为 OpenAI 格式
|
|
6
|
-
* Claude: { type: "image", source: { type: "base64", media_type: "image/jpeg", data: "..." } }
|
|
7
|
-
* OpenAI: { type: "image_url", image_url: { url: "data:image/jpeg;base64,..." } }
|
|
8
|
-
*/
|
|
9
|
-
const convertClaudeImageToOpenAI = (block) => {
|
|
10
|
-
if (!block || typeof block !== 'object' || block.type !== 'image') {
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
const source = block.source;
|
|
14
|
-
if (!source || typeof source !== 'object') {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
let imageUrl = null;
|
|
18
|
-
// 处理 base64 编码的图像
|
|
19
|
-
if (source.type === 'base64' && source.data && source.media_type) {
|
|
20
|
-
imageUrl = `data:${source.media_type};base64,${source.data}`;
|
|
21
|
-
}
|
|
22
|
-
// 处理 URL 格式的图像
|
|
23
|
-
else if (source.type === 'url' && source.url) {
|
|
24
|
-
imageUrl = source.url;
|
|
25
|
-
}
|
|
26
|
-
// 处理 file_id(如果有的话)
|
|
27
|
-
else if (source.type === 'file' && source.file_id) {
|
|
28
|
-
// file_id 需要特殊处理,这里先保留为占位符
|
|
29
|
-
imageUrl = null; // 需要调用方处理 file_id
|
|
30
|
-
}
|
|
31
|
-
if (!imageUrl) {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
return {
|
|
35
|
-
type: 'image_url',
|
|
36
|
-
image_url: {
|
|
37
|
-
url: imageUrl,
|
|
38
|
-
detail: 'auto', // 默认使用 auto,可以根据需要调整
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* 将 OpenAI 图像 content block 转换为 Claude 格式
|
|
44
|
-
* OpenAI: { type: "image_url", image_url: { url: "..." } }
|
|
45
|
-
* Claude: { type: "image", source: { type: "base64" | "url", media_type: "...", data/ url: "..." } }
|
|
46
|
-
*/
|
|
47
|
-
const convertOpenAIImageToClaude = (block) => {
|
|
48
|
-
var _a;
|
|
49
|
-
if (!block || typeof block !== 'object' || block.type !== 'image_url') {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
const imageUrl = (_a = block.image_url) === null || _a === void 0 ? void 0 : _a.url;
|
|
53
|
-
if (!imageUrl || typeof imageUrl !== 'string') {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
// 检查是否是 data URL (base64)
|
|
57
|
-
if (imageUrl.startsWith('data:')) {
|
|
58
|
-
const match = imageUrl.match(/^data:([^;]+);base64,(.+)$/);
|
|
59
|
-
if (match) {
|
|
60
|
-
return {
|
|
61
|
-
type: 'image',
|
|
62
|
-
source: {
|
|
63
|
-
type: 'base64',
|
|
64
|
-
media_type: match[1],
|
|
65
|
-
data: match[2],
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
// 否则作为 URL 处理
|
|
71
|
-
return {
|
|
72
|
-
type: 'image',
|
|
73
|
-
source: {
|
|
74
|
-
type: 'url',
|
|
75
|
-
url: imageUrl,
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
};
|
|
79
|
-
const toTextContent = (content) => {
|
|
80
|
-
if (typeof content === 'string')
|
|
81
|
-
return content;
|
|
82
|
-
if (!Array.isArray(content))
|
|
83
|
-
return null;
|
|
84
|
-
const parts = [];
|
|
85
|
-
for (const item of content) {
|
|
86
|
-
if (item && typeof item === 'object') {
|
|
87
|
-
const block = item;
|
|
88
|
-
// 只提取文本内容,忽略图像和其他类型
|
|
89
|
-
if (block.type === 'text' && 'text' in block && typeof block.text === 'string') {
|
|
90
|
-
parts.push(block.text);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return parts.length > 0 ? parts.join('') : null;
|
|
95
|
-
};
|
|
96
|
-
/**
|
|
97
|
-
* 将 Claude 的 tool_choice 映射到 OpenAI 格式
|
|
98
|
-
* Claude: "auto" | "any" | {type: "tool", name: string}
|
|
99
|
-
* OpenAI: "auto" | "none" | "required" | {type: "function", function: {name: string}}
|
|
100
|
-
*/
|
|
101
|
-
const mapClaudeToolChoiceToOpenAI = (toolChoice) => {
|
|
102
|
-
var _a;
|
|
103
|
-
// 字符串类型直接映射
|
|
104
|
-
if (toolChoice === 'auto' || toolChoice === 'none') {
|
|
105
|
-
return toolChoice;
|
|
106
|
-
}
|
|
107
|
-
// Claude 的 "any" 映射到 OpenAI 的 "required"
|
|
108
|
-
if (toolChoice === 'any' || toolChoice === 'required') {
|
|
109
|
-
return 'required';
|
|
110
|
-
}
|
|
111
|
-
// 对象类型:{type: "tool", name: "tool_name"} -> {type: "function", function: {name: "tool_name"}}
|
|
112
|
-
if (toolChoice && typeof toolChoice === 'object') {
|
|
113
|
-
const tc = toolChoice;
|
|
114
|
-
// Claude 格式
|
|
115
|
-
if (tc.type === 'tool' && tc.name) {
|
|
116
|
-
return {
|
|
117
|
-
type: 'function',
|
|
118
|
-
function: { name: tc.name },
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
// OpenAI 格式(已经是正确格式)
|
|
122
|
-
if (tc.type === 'function' && ((_a = tc.function) === null || _a === void 0 ? void 0 : _a.name)) {
|
|
123
|
-
return toolChoice;
|
|
124
|
-
}
|
|
125
|
-
// 兼容旧的 name 字段格式
|
|
126
|
-
if (tc.name && !tc.type) {
|
|
127
|
-
return {
|
|
128
|
-
type: 'function',
|
|
129
|
-
function: { name: tc.name },
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return toolChoice;
|
|
134
|
-
};
|
|
135
|
-
const convertOpenAIUsageToClaude = (usage) => {
|
|
136
|
-
var _a;
|
|
137
|
-
const cached = ((_a = usage === null || usage === void 0 ? void 0 : usage.prompt_tokens_details) === null || _a === void 0 ? void 0 : _a.cached_tokens) || 0;
|
|
138
|
-
return {
|
|
139
|
-
input_tokens: ((usage === null || usage === void 0 ? void 0 : usage.prompt_tokens) || 0) - cached,
|
|
140
|
-
output_tokens: (usage === null || usage === void 0 ? void 0 : usage.completion_tokens) || 0,
|
|
141
|
-
cache_read_input_tokens: cached,
|
|
142
|
-
};
|
|
143
|
-
};
|
|
144
|
-
exports.convertOpenAIUsageToClaude = convertOpenAIUsageToClaude;
|
|
145
|
-
/**
|
|
146
|
-
* 将 OpenAI 的 finish_reason 映射到 Claude 的 stop_reason
|
|
147
|
-
* OpenAI: "stop" | "length" | "tool_calls" | "content_filter"
|
|
148
|
-
* Claude: "end_turn" | "max_tokens" | "tool_use" | "stop_sequence" | "max_thinking_length"
|
|
149
|
-
*/
|
|
150
|
-
const mapStopReason = (finishReason) => {
|
|
151
|
-
switch (finishReason) {
|
|
152
|
-
case 'stop':
|
|
153
|
-
return 'end_turn';
|
|
154
|
-
case 'length':
|
|
155
|
-
return 'max_tokens';
|
|
156
|
-
case 'tool_calls':
|
|
157
|
-
return 'tool_use';
|
|
158
|
-
case 'content_filter':
|
|
159
|
-
return 'content_filter';
|
|
160
|
-
default:
|
|
161
|
-
return 'end_turn';
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
exports.mapStopReason = mapStopReason;
|
|
165
|
-
/**
|
|
166
|
-
* 将 Claude 的 stop_reason 映射到 OpenAI 的 finish_reason
|
|
167
|
-
* Claude: "end_turn" | "max_tokens" | "tool_use" | "stop_sequence" | "max_thinking_length"
|
|
168
|
-
* OpenAI: "stop" | "length" | "tool_calls" | "content_filter"
|
|
169
|
-
*/
|
|
170
|
-
const mapClaudeStopReasonToOpenAI = (stopReason) => {
|
|
171
|
-
switch (stopReason) {
|
|
172
|
-
case 'end_turn':
|
|
173
|
-
return 'stop';
|
|
174
|
-
case 'max_tokens':
|
|
175
|
-
case 'max_thinking_length': // Claude 的思考预算用完,映射为 length
|
|
176
|
-
return 'length';
|
|
177
|
-
case 'tool_use':
|
|
178
|
-
return 'tool_calls';
|
|
179
|
-
case 'stop_sequence':
|
|
180
|
-
return 'stop';
|
|
181
|
-
case 'content_filter':
|
|
182
|
-
return 'content_filter';
|
|
183
|
-
default:
|
|
184
|
-
return 'stop';
|
|
185
|
-
}
|
|
186
|
-
};
|
|
187
|
-
exports.mapClaudeStopReasonToOpenAI = mapClaudeStopReasonToOpenAI;
|
|
188
|
-
/**
|
|
189
|
-
* 检查模型是否需要使用 developer 角色而不是 system 角色
|
|
190
|
-
* 某些 OpenAI 兼容的 API (如 DeepSeek) 不支持 system 角色,需要使用 developer
|
|
191
|
-
*/
|
|
192
|
-
const shouldUseDeveloperRole = (model) => {
|
|
193
|
-
if (!model)
|
|
194
|
-
return false;
|
|
195
|
-
const lowerModel = model.toLowerCase();
|
|
196
|
-
// DeepSeek 模型使用 developer 角色
|
|
197
|
-
if (lowerModel.includes('deepseek')) {
|
|
198
|
-
return true;
|
|
199
|
-
}
|
|
200
|
-
// 其他可能需要 developer 角色的模型可以在这里添加
|
|
201
|
-
// 例如:某些国内的 GPT 兼容 API
|
|
202
|
-
return false;
|
|
203
|
-
};
|
|
204
|
-
/**
|
|
205
|
-
* 智能修复 messages 数组,确保最后一条消息是 role: user
|
|
206
|
-
* OpenAI Chat API 要求对话必须以用户消息结束
|
|
207
|
-
*
|
|
208
|
-
* 处理场景:
|
|
209
|
-
* 1. 最后是 assistant 消息(带 tool_calls):添加用户消息请求执行工具
|
|
210
|
-
* 2. 最后是 assistant 消息(不带 tool_calls):添加用户继续提示
|
|
211
|
-
* 3. 最后是 tool 消息:添加用户消息请求处理工具结果
|
|
212
|
-
* 4. 最后是 system/developer 消息:添加初始用户消息
|
|
213
|
-
* 5. 最后已经是 user 消息:不处理
|
|
214
|
-
*/
|
|
215
|
-
const ensureLastMessageIsUser = (messages) => {
|
|
216
|
-
if (!messages || messages.length === 0) {
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
const lastMessage = messages[messages.length - 1];
|
|
220
|
-
const lastRole = lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.role;
|
|
221
|
-
// 如果最后一条已经是 user,无需处理
|
|
222
|
-
if (lastRole === 'user') {
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
// 场景1: 最后是 assistant 消息且带有 tool_calls
|
|
226
|
-
// 这种情况下,通常后面应该跟 tool 消息,但如果没有,我们需要添加一个用户消息
|
|
227
|
-
if (lastRole === 'assistant' && lastMessage.tool_calls && Array.isArray(lastMessage.tool_calls) && lastMessage.tool_calls.length > 0) {
|
|
228
|
-
messages.push({
|
|
229
|
-
role: 'user',
|
|
230
|
-
content: 'Please proceed with the tool calls.'
|
|
231
|
-
});
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
// 场景2: 最后是 assistant 消息(不带 tool_calls)
|
|
235
|
-
if (lastRole === 'assistant') {
|
|
236
|
-
messages.push({
|
|
237
|
-
role: 'user',
|
|
238
|
-
content: 'Please continue.'
|
|
239
|
-
});
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
// 场景3: 最后是 tool 消息
|
|
243
|
-
if (lastRole === 'tool') {
|
|
244
|
-
messages.push({
|
|
245
|
-
role: 'user',
|
|
246
|
-
content: 'Please analyze the tool results and continue.'
|
|
247
|
-
});
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
// 场景4: 最后是 system/developer 消息
|
|
251
|
-
if (lastRole === 'system' || lastRole === 'developer') {
|
|
252
|
-
messages.push({
|
|
253
|
-
role: 'user',
|
|
254
|
-
content: 'Hello, I need your assistance.'
|
|
255
|
-
});
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
// 其他未知角色,添加通用用户消息
|
|
259
|
-
messages.push({
|
|
260
|
-
role: 'user',
|
|
261
|
-
content: 'Please continue.'
|
|
262
|
-
});
|
|
263
|
-
};
|
|
264
|
-
const transformClaudeRequestToOpenAIChat = (body, targetModel) => {
|
|
265
|
-
var _a, _b, _c;
|
|
266
|
-
const messages = [];
|
|
267
|
-
const useDeveloperRole = shouldUseDeveloperRole(targetModel);
|
|
268
|
-
const systemRoleName = useDeveloperRole ? 'developer' : 'system';
|
|
269
|
-
if (body.system) {
|
|
270
|
-
// 处理 system 字段:字符串或数组
|
|
271
|
-
if (typeof body.system === 'string') {
|
|
272
|
-
messages.push({ role: systemRoleName, content: body.system });
|
|
273
|
-
}
|
|
274
|
-
else if (Array.isArray(body.system)) {
|
|
275
|
-
// system 是数组,提取文本内容
|
|
276
|
-
const systemTexts = [];
|
|
277
|
-
for (const block of body.system) {
|
|
278
|
-
if (block && typeof block === 'object') {
|
|
279
|
-
const blk = block;
|
|
280
|
-
if (blk.type === 'text' && typeof blk.text === 'string') {
|
|
281
|
-
systemTexts.push(blk.text);
|
|
282
|
-
}
|
|
283
|
-
// 注意:OpenAI 的 system 角色不支持图像,忽略图像块
|
|
284
|
-
// 缓存控制块也忽略(OpenAI 不支持)
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
if (systemTexts.length > 0) {
|
|
288
|
-
messages.push({ role: systemRoleName, content: systemTexts.join('\n\n') });
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
else if (typeof body.system === 'object') {
|
|
292
|
-
// 单个 system block
|
|
293
|
-
const text = toTextContent([body.system]);
|
|
294
|
-
if (text) {
|
|
295
|
-
messages.push({ role: systemRoleName, content: text });
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
if (Array.isArray(body.messages)) {
|
|
300
|
-
for (const message of body.messages) {
|
|
301
|
-
// 映射 system 角色到 developer (如果需要)
|
|
302
|
-
const mappedRole = (message.role === 'system' && useDeveloperRole) ? 'developer' : message.role;
|
|
303
|
-
if (typeof message.content === 'string' || message.content === null) {
|
|
304
|
-
// 处理 content 为 null 的情况,使用空字符串替代
|
|
305
|
-
const content = message.content === null ? '' : message.content;
|
|
306
|
-
messages.push({ role: mappedRole, content });
|
|
307
|
-
continue;
|
|
308
|
-
}
|
|
309
|
-
if (Array.isArray(message.content)) {
|
|
310
|
-
const textParts = [];
|
|
311
|
-
const imageParts = []; // OpenAI 格式的图像内容
|
|
312
|
-
const toolCalls = [];
|
|
313
|
-
const toolResultMessages = [];
|
|
314
|
-
const thinkingParts = [];
|
|
315
|
-
for (const block of message.content) {
|
|
316
|
-
if (block && typeof block === 'object') {
|
|
317
|
-
const blockType = block.type;
|
|
318
|
-
// 处理文本内容
|
|
319
|
-
if (blockType === 'text' && typeof block.text === 'string') {
|
|
320
|
-
textParts.push(block.text);
|
|
321
|
-
}
|
|
322
|
-
// 处理图像内容 - 转换为 OpenAI 格式
|
|
323
|
-
if (blockType === 'image') {
|
|
324
|
-
const openaiImage = convertClaudeImageToOpenAI(block);
|
|
325
|
-
if (openaiImage) {
|
|
326
|
-
imageParts.push(openaiImage);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
// 处理 thinking content block(转换为文本,因为 OpenAI Chat 不直接支持)
|
|
330
|
-
if (blockType === 'thinking' && typeof block.thinking === 'string') {
|
|
331
|
-
thinkingParts.push(block.thinking);
|
|
332
|
-
}
|
|
333
|
-
// 处理工具使用
|
|
334
|
-
if (blockType === 'tool_use') {
|
|
335
|
-
const toolId = block.id || `tool_${toolCalls.length + 1}`;
|
|
336
|
-
const toolName = block.name || 'tool';
|
|
337
|
-
const input = (_a = block.input) !== null && _a !== void 0 ? _a : {};
|
|
338
|
-
toolCalls.push({
|
|
339
|
-
id: toolId,
|
|
340
|
-
type: 'function',
|
|
341
|
-
function: {
|
|
342
|
-
name: toolName,
|
|
343
|
-
arguments: JSON.stringify(input),
|
|
344
|
-
},
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
// 处理工具结果
|
|
348
|
-
if (blockType === 'tool_result') {
|
|
349
|
-
const toolCallId = block.tool_use_id || block.id;
|
|
350
|
-
const toolContent = block.content;
|
|
351
|
-
const isError = block.is_error;
|
|
352
|
-
toolResultMessages.push(Object.assign({ role: 'tool', tool_call_id: toolCallId, content: typeof toolContent === 'string' ? toolContent : JSON.stringify(toolContent !== null && toolContent !== void 0 ? toolContent : {}) }, (isError !== undefined && { is_error: isError })));
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
// 构建消息内容
|
|
357
|
-
// 如果有图像,content 必须是数组格式;否则可以是字符串
|
|
358
|
-
let openaiMessage;
|
|
359
|
-
if (imageParts.length > 0) {
|
|
360
|
-
// 有图像内容,使用数组格式
|
|
361
|
-
const contentArray = [];
|
|
362
|
-
// 添加文本部分(如果有)
|
|
363
|
-
if (textParts.length > 0) {
|
|
364
|
-
contentArray.push({
|
|
365
|
-
type: 'text',
|
|
366
|
-
text: textParts.join(''),
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
// 添加图像部分
|
|
370
|
-
contentArray.push(...imageParts);
|
|
371
|
-
// 添加 thinking 内容(如果有)
|
|
372
|
-
if (thinkingParts.length > 0) {
|
|
373
|
-
const thinkingText = thinkingParts.join('\n');
|
|
374
|
-
contentArray.push({
|
|
375
|
-
type: 'text',
|
|
376
|
-
text: `<thinking>\n${thinkingText}\n</thinking>`,
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
openaiMessage = {
|
|
380
|
-
role: mappedRole,
|
|
381
|
-
content: contentArray,
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
else {
|
|
385
|
-
// 没有图像,使用字符串格式(更简单)
|
|
386
|
-
let content = textParts.length > 0 ? textParts.join('') : '';
|
|
387
|
-
// 如果有 thinking 内容,将其作为前缀添加到文本中(用特殊标记包裹)
|
|
388
|
-
if (thinkingParts.length > 0) {
|
|
389
|
-
const thinkingText = thinkingParts.join('\n');
|
|
390
|
-
content = `<thinking>\n${thinkingText}\n</thinking>\n${content}`;
|
|
391
|
-
}
|
|
392
|
-
openaiMessage = {
|
|
393
|
-
role: mappedRole,
|
|
394
|
-
content: content || '', // 确保不为 undefined
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
if (toolCalls.length > 0) {
|
|
398
|
-
openaiMessage.tool_calls = toolCalls;
|
|
399
|
-
}
|
|
400
|
-
messages.push(openaiMessage);
|
|
401
|
-
toolResultMessages.forEach((toolMessage) => messages.push(toolMessage));
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
// 智能修复:确保最后一条消息是 role: user
|
|
406
|
-
// OpenAI API 要求对话必须以用户消息结束
|
|
407
|
-
ensureLastMessageIsUser(messages);
|
|
408
|
-
const openaiBody = {
|
|
409
|
-
model: targetModel || body.model,
|
|
410
|
-
messages,
|
|
411
|
-
};
|
|
412
|
-
if (typeof body.temperature === 'number')
|
|
413
|
-
openaiBody.temperature = body.temperature;
|
|
414
|
-
if (typeof body.top_p === 'number')
|
|
415
|
-
openaiBody.top_p = body.top_p;
|
|
416
|
-
if (typeof body.max_tokens === 'number')
|
|
417
|
-
openaiBody.max_tokens = body.max_tokens;
|
|
418
|
-
if (Array.isArray(body.stop_sequences))
|
|
419
|
-
openaiBody.stop = body.stop_sequences;
|
|
420
|
-
if (body.tools) {
|
|
421
|
-
openaiBody.tools = body.tools.map((tool) => ({
|
|
422
|
-
type: 'function',
|
|
423
|
-
function: {
|
|
424
|
-
name: tool.name,
|
|
425
|
-
description: tool.description,
|
|
426
|
-
parameters: tool.input_schema,
|
|
427
|
-
},
|
|
428
|
-
}));
|
|
429
|
-
}
|
|
430
|
-
if (body.tool_choice) {
|
|
431
|
-
openaiBody.tool_choice = mapClaudeToolChoiceToOpenAI(body.tool_choice);
|
|
432
|
-
}
|
|
433
|
-
if (body.stream === true) {
|
|
434
|
-
openaiBody.stream = true;
|
|
435
|
-
openaiBody.stream_options = { include_usage: true };
|
|
436
|
-
}
|
|
437
|
-
// 处理 thinking/reasoning 配置的转换
|
|
438
|
-
// Claude: thinking: { type: "enabled" | "disabled" | "auto", budget_tokens?: number }
|
|
439
|
-
// OpenAI Chat: thinking: { type: "enabled" | "disabled" | "auto" }
|
|
440
|
-
// OpenAI Responses: thinking + reasoning (effort)
|
|
441
|
-
// DeepSeek: thinking: { type: "enabled" | "disabled" | "auto" }
|
|
442
|
-
if (body.thinking && typeof body.thinking === 'object') {
|
|
443
|
-
const claudeThinking = body.thinking;
|
|
444
|
-
// 为所有 OpenAI 兼容 API 添加 thinking 配置
|
|
445
|
-
if (claudeThinking.type) {
|
|
446
|
-
openaiBody.thinking = { type: claudeThinking.type };
|
|
447
|
-
}
|
|
448
|
-
// 为 OpenAI Responses API 添加 reasoning 配置
|
|
449
|
-
// 映射关系:enabled->medium, disabled->low, auto->low
|
|
450
|
-
if (claudeThinking.type) {
|
|
451
|
-
const effortMap = {
|
|
452
|
-
'enabled': 'medium',
|
|
453
|
-
'disabled': 'low',
|
|
454
|
-
'auto': 'low'
|
|
455
|
-
};
|
|
456
|
-
openaiBody.reasoning = {
|
|
457
|
-
effort: (effortMap[claudeThinking.type] || 'medium')
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
// 处理直接的 reasoning_effort 字段(来自请求体)
|
|
462
|
-
if (body.reasoning_effort || ((_b = body.reasoning) === null || _b === void 0 ? void 0 : _b.effort)) {
|
|
463
|
-
const effort = body.reasoning_effort || ((_c = body.reasoning) === null || _c === void 0 ? void 0 : _c.effort);
|
|
464
|
-
if (typeof effort === 'string') {
|
|
465
|
-
openaiBody.reasoning = {
|
|
466
|
-
effort: effort
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
return openaiBody;
|
|
471
|
-
};
|
|
472
|
-
exports.transformClaudeRequestToOpenAIChat = transformClaudeRequestToOpenAIChat;
|
|
473
|
-
/**
|
|
474
|
-
* 从 OpenAI 消息内容中提取文本和图像
|
|
475
|
-
* 支持字符串格式和数组格式
|
|
476
|
-
*/
|
|
477
|
-
const extractOpenAIContent = (content) => {
|
|
478
|
-
const result = { text: '', images: [] };
|
|
479
|
-
if (typeof content === 'string') {
|
|
480
|
-
result.text = content;
|
|
481
|
-
return result;
|
|
482
|
-
}
|
|
483
|
-
if (!Array.isArray(content)) {
|
|
484
|
-
return result;
|
|
485
|
-
}
|
|
486
|
-
for (const item of content) {
|
|
487
|
-
if (item && typeof item === 'object') {
|
|
488
|
-
const block = item;
|
|
489
|
-
// 提取文本内容
|
|
490
|
-
if (block.type === 'text' && typeof block.text === 'string') {
|
|
491
|
-
result.text += block.text;
|
|
492
|
-
}
|
|
493
|
-
// 提取图像内容
|
|
494
|
-
if (block.type === 'image_url') {
|
|
495
|
-
const claudeImage = convertOpenAIImageToClaude(block);
|
|
496
|
-
if (claudeImage) {
|
|
497
|
-
result.images.push(claudeImage);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
return result;
|
|
503
|
-
};
|
|
504
|
-
const transformOpenAIChatResponseToClaude = (body) => {
|
|
505
|
-
var _a, _b, _c;
|
|
506
|
-
const choice = Array.isArray(body === null || body === void 0 ? void 0 : body.choices) ? body.choices[0] : null;
|
|
507
|
-
const message = (choice === null || choice === void 0 ? void 0 : choice.message) || {};
|
|
508
|
-
const contentBlocks = [];
|
|
509
|
-
// 提取文本和图像内容
|
|
510
|
-
const extractedContent = extractOpenAIContent(message.content);
|
|
511
|
-
// 添加图像内容块
|
|
512
|
-
for (const image of extractedContent.images) {
|
|
513
|
-
contentBlocks.push(image);
|
|
514
|
-
}
|
|
515
|
-
// 添加文本内容块
|
|
516
|
-
if (extractedContent.text) {
|
|
517
|
-
contentBlocks.push({ type: 'text', text: extractedContent.text });
|
|
518
|
-
}
|
|
519
|
-
// 处理 thinking 内容(如果 OpenAI 返回了独立的 thinking 字段)
|
|
520
|
-
// OpenAI Chat Completions API 可能在 message 中包含 thinking
|
|
521
|
-
if (message.thinking && typeof message.thinking === 'string') {
|
|
522
|
-
contentBlocks.unshift({ type: 'thinking', thinking: message.thinking });
|
|
523
|
-
}
|
|
524
|
-
else if (message.thinking_content) {
|
|
525
|
-
contentBlocks.unshift({ type: 'thinking', thinking: message.thinking_content });
|
|
526
|
-
}
|
|
527
|
-
// 处理 OpenAI Responses API 的 reasoning.summary 和 reasoning content
|
|
528
|
-
// Responses API 可能在 output 数组中包含 reasoning 内容
|
|
529
|
-
if (Array.isArray(body === null || body === void 0 ? void 0 : body.output)) {
|
|
530
|
-
for (const outputItem of body.output) {
|
|
531
|
-
// 处理 reasoning summary
|
|
532
|
-
if (outputItem.type === 'reasoning' && outputItem.content) {
|
|
533
|
-
for (const part of outputItem.content) {
|
|
534
|
-
if (part.type === 'summary_text' && part.text) {
|
|
535
|
-
contentBlocks.unshift({ type: 'thinking', thinking: part.text });
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
// 处理 message 中的 thinking/reasoning
|
|
540
|
-
if (outputItem.type === 'message' && Array.isArray(outputItem.content)) {
|
|
541
|
-
for (const part of outputItem.content) {
|
|
542
|
-
if (part.type === 'thinking' && part.text) {
|
|
543
|
-
contentBlocks.unshift({ type: 'thinking', thinking: part.text });
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
// 处理 reasoning.summary 字段(OpenAI Responses API)
|
|
550
|
-
if (((_a = body === null || body === void 0 ? void 0 : body.reasoning) === null || _a === void 0 ? void 0 : _a.summary) && typeof body.reasoning.summary === 'string') {
|
|
551
|
-
contentBlocks.unshift({ type: 'thinking', thinking: body.reasoning.summary });
|
|
552
|
-
}
|
|
553
|
-
if (Array.isArray(message.tool_calls)) {
|
|
554
|
-
for (const toolCall of message.tool_calls) {
|
|
555
|
-
const toolName = ((_b = toolCall === null || toolCall === void 0 ? void 0 : toolCall.function) === null || _b === void 0 ? void 0 : _b.name) || 'tool';
|
|
556
|
-
let input = {};
|
|
557
|
-
if ((_c = toolCall === null || toolCall === void 0 ? void 0 : toolCall.function) === null || _c === void 0 ? void 0 : _c.arguments) {
|
|
558
|
-
try {
|
|
559
|
-
input = JSON.parse(toolCall.function.arguments);
|
|
560
|
-
}
|
|
561
|
-
catch (_d) {
|
|
562
|
-
input = toolCall.function.arguments;
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
contentBlocks.push({
|
|
566
|
-
type: 'tool_use',
|
|
567
|
-
id: toolCall.id,
|
|
568
|
-
name: toolName,
|
|
569
|
-
input,
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
const usage = (body === null || body === void 0 ? void 0 : body.usage) ? (0, exports.convertOpenAIUsageToClaude)(body.usage) : null;
|
|
574
|
-
return {
|
|
575
|
-
id: body === null || body === void 0 ? void 0 : body.id,
|
|
576
|
-
type: 'message',
|
|
577
|
-
role: 'assistant',
|
|
578
|
-
model: body === null || body === void 0 ? void 0 : body.model,
|
|
579
|
-
content: contentBlocks,
|
|
580
|
-
stop_reason: (0, exports.mapStopReason)(choice === null || choice === void 0 ? void 0 : choice.finish_reason),
|
|
581
|
-
stop_sequence: null,
|
|
582
|
-
usage: usage || {
|
|
583
|
-
input_tokens: 0,
|
|
584
|
-
output_tokens: 0,
|
|
585
|
-
cache_read_input_tokens: 0,
|
|
586
|
-
},
|
|
587
|
-
};
|
|
588
|
-
};
|
|
589
|
-
exports.transformOpenAIChatResponseToClaude = transformOpenAIChatResponseToClaude;
|
|
590
|
-
const transformClaudeResponseToOpenAIChat = (body) => {
|
|
591
|
-
const content = (body === null || body === void 0 ? void 0 : body.content) || [];
|
|
592
|
-
let textContent = '';
|
|
593
|
-
const imageContents = []; // OpenAI 格式的图像
|
|
594
|
-
const toolCalls = [];
|
|
595
|
-
let thinkingContent = '';
|
|
596
|
-
for (const block of content) {
|
|
597
|
-
if ((block === null || block === void 0 ? void 0 : block.type) === 'text') {
|
|
598
|
-
textContent += block.text || '';
|
|
599
|
-
}
|
|
600
|
-
else if ((block === null || block === void 0 ? void 0 : block.type) === 'image') {
|
|
601
|
-
// 转换 Claude 图像为 OpenAI 格式
|
|
602
|
-
const openaiImage = convertClaudeImageToOpenAI(block);
|
|
603
|
-
if (openaiImage) {
|
|
604
|
-
imageContents.push(openaiImage);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
else if ((block === null || block === void 0 ? void 0 : block.type) === 'thinking') {
|
|
608
|
-
thinkingContent += block.thinking || '';
|
|
609
|
-
}
|
|
610
|
-
else if ((block === null || block === void 0 ? void 0 : block.type) === 'tool_use') {
|
|
611
|
-
toolCalls.push({
|
|
612
|
-
id: block.id,
|
|
613
|
-
type: 'function',
|
|
614
|
-
function: {
|
|
615
|
-
name: block.name || 'tool',
|
|
616
|
-
arguments: typeof block.input === 'string' ? block.input : JSON.stringify(block.input || {}),
|
|
617
|
-
},
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
// 构建消息内容
|
|
622
|
-
// 如果有图像,使用数组格式;否则使用字符串格式
|
|
623
|
-
let message;
|
|
624
|
-
if (imageContents.length > 0) {
|
|
625
|
-
// 有图像,使用数组格式
|
|
626
|
-
const contentArray = [];
|
|
627
|
-
// 添加文本
|
|
628
|
-
if (textContent) {
|
|
629
|
-
contentArray.push({
|
|
630
|
-
type: 'text',
|
|
631
|
-
text: textContent,
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
// 添加图像
|
|
635
|
-
contentArray.push(...imageContents);
|
|
636
|
-
message = {
|
|
637
|
-
role: 'assistant',
|
|
638
|
-
content: contentArray,
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
else {
|
|
642
|
-
// 没有图像,使用字符串格式
|
|
643
|
-
message = {
|
|
644
|
-
role: 'assistant',
|
|
645
|
-
content: textContent,
|
|
646
|
-
};
|
|
647
|
-
}
|
|
648
|
-
// 如果有 thinking 内容,添加到消息中
|
|
649
|
-
if (thinkingContent) {
|
|
650
|
-
message.thinking = thinkingContent;
|
|
651
|
-
}
|
|
652
|
-
if (toolCalls.length > 0) {
|
|
653
|
-
message.tool_calls = toolCalls;
|
|
654
|
-
}
|
|
655
|
-
const usage = (body === null || body === void 0 ? void 0 : body.usage) ? {
|
|
656
|
-
prompt_tokens: body.usage.input_tokens || 0,
|
|
657
|
-
completion_tokens: body.usage.output_tokens || 0,
|
|
658
|
-
total_tokens: (body.usage.input_tokens || 0) + (body.usage.output_tokens || 0),
|
|
659
|
-
} : undefined;
|
|
660
|
-
return {
|
|
661
|
-
id: body === null || body === void 0 ? void 0 : body.id,
|
|
662
|
-
object: 'chat.completion',
|
|
663
|
-
created: Math.floor(Date.now() / 1000),
|
|
664
|
-
model: body === null || body === void 0 ? void 0 : body.model,
|
|
665
|
-
choices: [{
|
|
666
|
-
index: 0,
|
|
667
|
-
message,
|
|
668
|
-
finish_reason: (0, exports.mapClaudeStopReasonToOpenAI)(body === null || body === void 0 ? void 0 : body.stop_reason),
|
|
669
|
-
}],
|
|
670
|
-
usage,
|
|
671
|
-
};
|
|
672
|
-
};
|
|
673
|
-
exports.transformClaudeResponseToOpenAIChat = transformClaudeResponseToOpenAIChat;
|
|
674
|
-
const extractTokenUsageFromOpenAIUsage = (usage) => {
|
|
675
|
-
if (!usage)
|
|
676
|
-
return undefined;
|
|
677
|
-
const converted = (0, exports.convertOpenAIUsageToClaude)(usage);
|
|
678
|
-
return {
|
|
679
|
-
inputTokens: converted.input_tokens,
|
|
680
|
-
outputTokens: converted.output_tokens,
|
|
681
|
-
totalTokens: usage.total_tokens,
|
|
682
|
-
cacheReadInputTokens: converted.cache_read_input_tokens,
|
|
683
|
-
};
|
|
684
|
-
};
|
|
685
|
-
exports.extractTokenUsageFromOpenAIUsage = extractTokenUsageFromOpenAIUsage;
|
|
686
|
-
const extractTokenUsageFromClaudeUsage = (usage) => {
|
|
687
|
-
var _a, _b;
|
|
688
|
-
if (!usage)
|
|
689
|
-
return undefined;
|
|
690
|
-
return {
|
|
691
|
-
inputTokens: (_a = usage.input_tokens) !== null && _a !== void 0 ? _a : 0,
|
|
692
|
-
outputTokens: (_b = usage.output_tokens) !== null && _b !== void 0 ? _b : 0,
|
|
693
|
-
totalTokens: usage.input_tokens !== undefined && usage.output_tokens !== undefined
|
|
694
|
-
? usage.input_tokens + usage.output_tokens
|
|
695
|
-
: undefined,
|
|
696
|
-
cacheReadInputTokens: usage.cache_read_input_tokens,
|
|
697
|
-
};
|
|
698
|
-
};
|
|
699
|
-
exports.extractTokenUsageFromClaudeUsage = extractTokenUsageFromClaudeUsage;
|
|
700
|
-
// ============================================================================
|
|
701
|
-
// OpenAI Chat Completions API ↔ OpenAI Responses API 转换
|
|
702
|
-
// ============================================================================
|
|
703
|
-
/**
|
|
704
|
-
* 将 OpenAI Chat Completions 请求转换为 OpenAI Responses API 请求
|
|
705
|
-
* Chat Completions: {model, messages, tools, temperature, ...}
|
|
706
|
-
* Responses: {model, input, instructions, tools, temperature, ...}
|
|
707
|
-
*/
|
|
708
|
-
const transformChatCompletionsToResponses = (body) => {
|
|
709
|
-
const responsesBody = {
|
|
710
|
-
model: body.model,
|
|
711
|
-
};
|
|
712
|
-
// 转换 messages -> input
|
|
713
|
-
if (Array.isArray(body.messages) && body.messages.length > 0) {
|
|
714
|
-
// 提取最后一条用户消息作为 input
|
|
715
|
-
const lastUserMessage = [...body.messages].reverse().find(m => m.role === 'user');
|
|
716
|
-
if (lastUserMessage) {
|
|
717
|
-
// 处理 content 格式
|
|
718
|
-
if (typeof lastUserMessage.content === 'string') {
|
|
719
|
-
responsesBody.input = lastUserMessage.content;
|
|
720
|
-
}
|
|
721
|
-
else if (Array.isArray(lastUserMessage.content)) {
|
|
722
|
-
// 保留数组格式(支持图像等)
|
|
723
|
-
responsesBody.input = lastUserMessage.content;
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
// 提取 system 消息作为 instructions
|
|
727
|
-
const systemMessage = body.messages.find((m) => m.role === 'system' || m.role === 'developer');
|
|
728
|
-
if (systemMessage && typeof systemMessage.content === 'string') {
|
|
729
|
-
responsesBody.instructions = systemMessage.content;
|
|
730
|
-
}
|
|
731
|
-
// 如果有对话历史,可以考虑设置 previous_response_id(需要从之前的响应中获取)
|
|
732
|
-
// 这里暂时不实现,因为需要维护对话状态
|
|
733
|
-
}
|
|
734
|
-
// 转换参数
|
|
735
|
-
if (typeof body.temperature === 'number') {
|
|
736
|
-
responsesBody.temperature = body.temperature;
|
|
737
|
-
}
|
|
738
|
-
if (typeof body.top_p === 'number') {
|
|
739
|
-
responsesBody.top_p = body.top_p;
|
|
740
|
-
}
|
|
741
|
-
if (typeof body.max_tokens === 'number') {
|
|
742
|
-
responsesBody.max_output_tokens = body.max_tokens;
|
|
743
|
-
}
|
|
744
|
-
// 转换 tools
|
|
745
|
-
if (Array.isArray(body.tools)) {
|
|
746
|
-
responsesBody.tools = body.tools;
|
|
747
|
-
}
|
|
748
|
-
if (body.tool_choice) {
|
|
749
|
-
responsesBody.tool_choice = body.tool_choice;
|
|
750
|
-
}
|
|
751
|
-
// 转换流式选项
|
|
752
|
-
if (body.stream === true) {
|
|
753
|
-
responsesBody.stream = true;
|
|
754
|
-
}
|
|
755
|
-
// 转换 reasoning 配置
|
|
756
|
-
if (body.reasoning && typeof body.reasoning === 'object') {
|
|
757
|
-
responsesBody.reasoning = body.reasoning;
|
|
758
|
-
}
|
|
759
|
-
// 转换其他配置
|
|
760
|
-
if (body.metadata) {
|
|
761
|
-
responsesBody.metadata = body.metadata;
|
|
762
|
-
}
|
|
763
|
-
return responsesBody;
|
|
764
|
-
};
|
|
765
|
-
exports.transformChatCompletionsToResponses = transformChatCompletionsToResponses;
|
|
766
|
-
/**
|
|
767
|
-
* 将 OpenAI Responses API 响应转换为 Chat Completions 格式
|
|
768
|
-
* Responses: {id, object: "response", output: [{type: "message", content: [...]}], usage, ...}
|
|
769
|
-
* Chat Completions: {id, object: "chat.completion", choices: [{message: {content, ...}}], usage, ...}
|
|
770
|
-
*/
|
|
771
|
-
const transformResponsesToChatCompletions = (body) => {
|
|
772
|
-
var _a;
|
|
773
|
-
if (!body || typeof body !== 'object') {
|
|
774
|
-
return body;
|
|
775
|
-
}
|
|
776
|
-
// 提取消息内容
|
|
777
|
-
let textContent = '';
|
|
778
|
-
const thinkingContent = [];
|
|
779
|
-
const toolCalls = [];
|
|
780
|
-
// 遍历 output 数组
|
|
781
|
-
if (Array.isArray(body.output)) {
|
|
782
|
-
for (const outputItem of body.output) {
|
|
783
|
-
if (outputItem.type === 'message' && Array.isArray(outputItem.content)) {
|
|
784
|
-
for (const part of outputItem.content) {
|
|
785
|
-
// 处理文本输出
|
|
786
|
-
if (part.type === 'output_text' && typeof part.text === 'string') {
|
|
787
|
-
textContent += part.text;
|
|
788
|
-
}
|
|
789
|
-
// 处理思考内容
|
|
790
|
-
if (part.type === 'thinking' && typeof part.text === 'string') {
|
|
791
|
-
thinkingContent.push(part.text);
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
// 处理 reasoning summary
|
|
796
|
-
if (outputItem.type === 'reasoning' && Array.isArray(outputItem.content)) {
|
|
797
|
-
for (const part of outputItem.content) {
|
|
798
|
-
if (part.type === 'summary_text' && typeof part.text === 'string') {
|
|
799
|
-
thinkingContent.push(part.text);
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
// 处理工具调用(如果有)
|
|
804
|
-
if (outputItem.type === 'function_call') {
|
|
805
|
-
toolCalls.push({
|
|
806
|
-
id: outputItem.id || `call_${toolCalls.length}`,
|
|
807
|
-
type: 'function',
|
|
808
|
-
function: {
|
|
809
|
-
name: outputItem.name || 'unknown',
|
|
810
|
-
arguments: outputItem.arguments || '{}',
|
|
811
|
-
},
|
|
812
|
-
});
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
// 构建消息对象
|
|
817
|
-
const message = {
|
|
818
|
-
role: 'assistant',
|
|
819
|
-
content: textContent,
|
|
820
|
-
};
|
|
821
|
-
// 添加 thinking 内容
|
|
822
|
-
if (thinkingContent.length > 0) {
|
|
823
|
-
message.thinking = thinkingContent.join('\n\n');
|
|
824
|
-
}
|
|
825
|
-
// 添加工具调用
|
|
826
|
-
if (toolCalls.length > 0) {
|
|
827
|
-
message.tool_calls = toolCalls;
|
|
828
|
-
}
|
|
829
|
-
// 转换 usage
|
|
830
|
-
const usage = body.usage ? {
|
|
831
|
-
prompt_tokens: body.usage.input_tokens || 0,
|
|
832
|
-
completion_tokens: body.usage.output_tokens || 0,
|
|
833
|
-
total_tokens: (body.usage.input_tokens || 0) + (body.usage.output_tokens || 0),
|
|
834
|
-
} : undefined;
|
|
835
|
-
// 转换 finish_reason
|
|
836
|
-
let finish_reason = 'stop';
|
|
837
|
-
if (body.status === 'incomplete') {
|
|
838
|
-
finish_reason = ((_a = body.incomplete_details) === null || _a === void 0 ? void 0 : _a.reason) === 'max_tokens' ? 'length' : 'stop';
|
|
839
|
-
}
|
|
840
|
-
return {
|
|
841
|
-
id: body.id,
|
|
842
|
-
object: 'chat.completion',
|
|
843
|
-
created: body.created_at || Math.floor(Date.now() / 1000),
|
|
844
|
-
model: body.model,
|
|
845
|
-
choices: [{
|
|
846
|
-
index: 0,
|
|
847
|
-
message,
|
|
848
|
-
finish_reason,
|
|
849
|
-
}],
|
|
850
|
-
usage,
|
|
851
|
-
};
|
|
852
|
-
};
|
|
853
|
-
exports.transformResponsesToChatCompletions = transformResponsesToChatCompletions;
|
|
854
|
-
/**
|
|
855
|
-
* 将 OpenAI Chat Completions 流式事件转换为 Responses API 流式事件格式
|
|
856
|
-
* 这主要用于解析不同格式的流式响应
|
|
857
|
-
*/
|
|
858
|
-
const normalizeOpenAIStreamEvent = (event) => {
|
|
859
|
-
const type = event.event;
|
|
860
|
-
// 如果是 Responses API 事件,直接返回
|
|
861
|
-
if (type && type.startsWith('response.')) {
|
|
862
|
-
return event;
|
|
863
|
-
}
|
|
864
|
-
// Chat Completions API 事件
|
|
865
|
-
// 实际的转换在 streaming.ts 的转换器中处理
|
|
866
|
-
return event;
|
|
867
|
-
};
|
|
868
|
-
exports.normalizeOpenAIStreamEvent = normalizeOpenAIStreamEvent;
|