aicodeswitch 2.0.11 → 2.1.2

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,6 +1,81 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.extractTokenUsageFromClaudeUsage = exports.extractTokenUsageFromOpenAIUsage = exports.transformClaudeResponseToOpenAIChat = exports.transformOpenAIChatResponseToClaude = exports.transformClaudeRequestToOpenAIChat = exports.mapStopReason = exports.convertOpenAIUsageToClaude = void 0;
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
+ };
4
79
  const toTextContent = (content) => {
5
80
  if (typeof content === 'string')
6
81
  return content;
@@ -8,21 +83,52 @@ const toTextContent = (content) => {
8
83
  return null;
9
84
  const parts = [];
10
85
  for (const item of content) {
11
- if (item && typeof item === 'object' && item.type === 'text' && typeof item.text === 'string') {
12
- parts.push(item.text);
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
+ }
13
92
  }
14
93
  }
15
94
  return parts.length > 0 ? parts.join('') : null;
16
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
+ */
17
101
  const mapClaudeToolChoiceToOpenAI = (toolChoice) => {
18
- if (toolChoice === 'auto' || toolChoice === 'none' || toolChoice === 'required') {
102
+ var _a;
103
+ // 字符串类型直接映射
104
+ if (toolChoice === 'auto' || toolChoice === 'none') {
19
105
  return toolChoice;
20
106
  }
21
- if (toolChoice && typeof toolChoice === 'object' && toolChoice.name) {
22
- return {
23
- type: 'function',
24
- function: { name: toolChoice.name },
25
- };
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
+ }
26
132
  }
27
133
  return toolChoice;
28
134
  };
@@ -36,6 +142,11 @@ const convertOpenAIUsageToClaude = (usage) => {
36
142
  };
37
143
  };
38
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
+ */
39
150
  const mapStopReason = (finishReason) => {
40
151
  switch (finishReason) {
41
152
  case 'stop':
@@ -51,6 +162,29 @@ const mapStopReason = (finishReason) => {
51
162
  }
52
163
  };
53
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;
54
188
  /**
55
189
  * 检查模型是否需要使用 developer 角色而不是 system 角色
56
190
  * 某些 OpenAI 兼容的 API (如 DeepSeek) 不支持 system 角色,需要使用 developer
@@ -128,14 +262,38 @@ const ensureLastMessageIsUser = (messages) => {
128
262
  });
129
263
  };
130
264
  const transformClaudeRequestToOpenAIChat = (body, targetModel) => {
131
- var _a;
265
+ var _a, _b, _c;
132
266
  const messages = [];
133
267
  const useDeveloperRole = shouldUseDeveloperRole(targetModel);
134
268
  const systemRoleName = useDeveloperRole ? 'developer' : 'system';
135
269
  if (body.system) {
136
- const systemText = toTextContent(body.system);
137
- if (systemText) {
138
- messages.push({ role: systemRoleName, content: systemText });
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
+ }
139
297
  }
140
298
  }
141
299
  if (Array.isArray(body.messages)) {
@@ -150,14 +308,30 @@ const transformClaudeRequestToOpenAIChat = (body, targetModel) => {
150
308
  }
151
309
  if (Array.isArray(message.content)) {
152
310
  const textParts = [];
311
+ const imageParts = []; // OpenAI 格式的图像内容
153
312
  const toolCalls = [];
154
313
  const toolResultMessages = [];
314
+ const thinkingParts = [];
155
315
  for (const block of message.content) {
156
316
  if (block && typeof block === 'object') {
157
- if (block.type === 'text' && typeof block.text === 'string') {
317
+ const blockType = block.type;
318
+ // 处理文本内容
319
+ if (blockType === 'text' && typeof block.text === 'string') {
158
320
  textParts.push(block.text);
159
321
  }
160
- if (block.type === 'tool_use') {
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') {
161
335
  const toolId = block.id || `tool_${toolCalls.length + 1}`;
162
336
  const toolName = block.name || 'tool';
163
337
  const input = (_a = block.input) !== null && _a !== void 0 ? _a : {};
@@ -170,23 +344,56 @@ const transformClaudeRequestToOpenAIChat = (body, targetModel) => {
170
344
  },
171
345
  });
172
346
  }
173
- if (block.type === 'tool_result') {
347
+ // 处理工具结果
348
+ if (blockType === 'tool_result') {
174
349
  const toolCallId = block.tool_use_id || block.id;
175
350
  const toolContent = block.content;
176
- toolResultMessages.push({
177
- role: 'tool',
178
- tool_call_id: toolCallId,
179
- content: typeof toolContent === 'string' ? toolContent : JSON.stringify(toolContent !== null && toolContent !== void 0 ? toolContent : {}),
180
- });
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 })));
181
353
  }
182
354
  }
183
355
  }
184
- // 避免 content 为 null,使用空字符串替代
185
- const content = textParts.length > 0 ? textParts.join('') : '';
186
- const openaiMessage = {
187
- role: mappedRole,
188
- content,
189
- };
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
+ }
190
397
  if (toolCalls.length > 0) {
191
398
  openaiMessage.tool_calls = toolCalls;
192
399
  }
@@ -227,40 +434,131 @@ const transformClaudeRequestToOpenAIChat = (body, targetModel) => {
227
434
  openaiBody.stream = true;
228
435
  openaiBody.stream_options = { include_usage: true };
229
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->minimal, auto->low
450
+ if (claudeThinking.type) {
451
+ const effortMap = {
452
+ 'enabled': 'medium',
453
+ 'disabled': 'minimal',
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
+ }
230
470
  return openaiBody;
231
471
  };
232
472
  exports.transformClaudeRequestToOpenAIChat = transformClaudeRequestToOpenAIChat;
233
- const extractOpenAIText = (content) => {
234
- if (typeof content === 'string')
235
- return content;
236
- if (!Array.isArray(content))
237
- return null;
238
- const parts = [];
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
+ }
239
486
  for (const item of content) {
240
- if (item && typeof item === 'object' && typeof item.text === 'string') {
241
- parts.push(item.text);
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
+ }
242
500
  }
243
501
  }
244
- return parts.length > 0 ? parts.join('') : null;
502
+ return result;
245
503
  };
246
504
  const transformOpenAIChatResponseToClaude = (body) => {
247
- var _a, _b;
505
+ var _a, _b, _c;
248
506
  const choice = Array.isArray(body === null || body === void 0 ? void 0 : body.choices) ? body.choices[0] : null;
249
507
  const message = (choice === null || choice === void 0 ? void 0 : choice.message) || {};
250
508
  const contentBlocks = [];
251
- const contentText = extractOpenAIText(message.content);
252
- if (contentText) {
253
- contentBlocks.push({ type: 'text', text: contentText });
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 });
254
552
  }
255
553
  if (Array.isArray(message.tool_calls)) {
256
554
  for (const toolCall of message.tool_calls) {
257
- const toolName = ((_a = toolCall === null || toolCall === void 0 ? void 0 : toolCall.function) === null || _a === void 0 ? void 0 : _a.name) || 'tool';
555
+ const toolName = ((_b = toolCall === null || toolCall === void 0 ? void 0 : toolCall.function) === null || _b === void 0 ? void 0 : _b.name) || 'tool';
258
556
  let input = {};
259
- if ((_b = toolCall === null || toolCall === void 0 ? void 0 : toolCall.function) === null || _b === void 0 ? void 0 : _b.arguments) {
557
+ if ((_c = toolCall === null || toolCall === void 0 ? void 0 : toolCall.function) === null || _c === void 0 ? void 0 : _c.arguments) {
260
558
  try {
261
559
  input = JSON.parse(toolCall.function.arguments);
262
560
  }
263
- catch (_c) {
561
+ catch (_d) {
264
562
  input = toolCall.function.arguments;
265
563
  }
266
564
  }
@@ -292,11 +590,23 @@ exports.transformOpenAIChatResponseToClaude = transformOpenAIChatResponseToClaud
292
590
  const transformClaudeResponseToOpenAIChat = (body) => {
293
591
  const content = (body === null || body === void 0 ? void 0 : body.content) || [];
294
592
  let textContent = '';
593
+ const imageContents = []; // OpenAI 格式的图像
295
594
  const toolCalls = [];
595
+ let thinkingContent = '';
296
596
  for (const block of content) {
297
597
  if ((block === null || block === void 0 ? void 0 : block.type) === 'text') {
298
598
  textContent += block.text || '';
299
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
+ }
300
610
  else if ((block === null || block === void 0 ? void 0 : block.type) === 'tool_use') {
301
611
  toolCalls.push({
302
612
  id: block.id,
@@ -308,10 +618,37 @@ const transformClaudeResponseToOpenAIChat = (body) => {
308
618
  });
309
619
  }
310
620
  }
311
- const message = {
312
- role: 'assistant',
313
- content: textContent,
314
- };
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
+ }
315
652
  if (toolCalls.length > 0) {
316
653
  message.tool_calls = toolCalls;
317
654
  }
@@ -328,7 +665,7 @@ const transformClaudeResponseToOpenAIChat = (body) => {
328
665
  choices: [{
329
666
  index: 0,
330
667
  message,
331
- finish_reason: (0, exports.mapStopReason)(body === null || body === void 0 ? void 0 : body.stop_reason),
668
+ finish_reason: (0, exports.mapClaudeStopReasonToOpenAI)(body === null || body === void 0 ? void 0 : body.stop_reason),
332
669
  }],
333
670
  usage,
334
671
  };
@@ -360,3 +697,172 @@ const extractTokenUsageFromClaudeUsage = (usage) => {
360
697
  };
361
698
  };
362
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;