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.
@@ -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;