aicodeswitch 2.0.8 → 2.0.10

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.
@@ -8,20 +8,45 @@ const stream_1 = require("stream");
8
8
  */
9
9
  class ChunkCollectorTransform extends stream_1.Transform {
10
10
  constructor() {
11
- super();
11
+ super({ writableObjectMode: true, readableObjectMode: true });
12
12
  Object.defineProperty(this, "chunks", {
13
13
  enumerable: true,
14
14
  configurable: true,
15
15
  writable: true,
16
16
  value: []
17
17
  });
18
+ Object.defineProperty(this, "errorEmitted", {
19
+ enumerable: true,
20
+ configurable: true,
21
+ writable: true,
22
+ value: false
23
+ });
24
+ this.on('error', (err) => {
25
+ console.error('[ChunkCollectorTransform] Stream error:', err);
26
+ this.errorEmitted = true;
27
+ });
18
28
  }
19
29
  _transform(chunk, _encoding, callback) {
20
- // 收集chunk数据
21
- this.chunks.push(chunk.toString('utf8'));
22
- // 将chunk传递给下一个stream
23
- this.push(chunk);
24
- callback();
30
+ if (this.errorEmitted) {
31
+ callback();
32
+ return;
33
+ }
34
+ try {
35
+ // 收集chunk数据 - 支持对象和Buffer/string
36
+ if (typeof chunk === 'object' && chunk !== null && !Buffer.isBuffer(chunk)) {
37
+ this.chunks.push(JSON.stringify(chunk));
38
+ }
39
+ else {
40
+ this.chunks.push(chunk.toString('utf8'));
41
+ }
42
+ // 将chunk传递给下一个stream
43
+ this.push(chunk);
44
+ callback();
45
+ }
46
+ catch (error) {
47
+ console.error('[ChunkCollectorTransform] Error in _transform:', error);
48
+ callback();
49
+ }
25
50
  }
26
51
  /**
27
52
  * 获取收集的所有chunks
@@ -44,7 +69,7 @@ exports.ChunkCollectorTransform = ChunkCollectorTransform;
44
69
  */
45
70
  class SSEEventCollectorTransform extends stream_1.Transform {
46
71
  constructor() {
47
- super();
72
+ super({ writableObjectMode: true, readableObjectMode: true });
48
73
  Object.defineProperty(this, "buffer", {
49
74
  enumerable: true,
50
75
  configurable: true,
@@ -66,22 +91,82 @@ class SSEEventCollectorTransform extends stream_1.Transform {
66
91
  writable: true,
67
92
  value: []
68
93
  });
94
+ Object.defineProperty(this, "errorEmitted", {
95
+ enumerable: true,
96
+ configurable: true,
97
+ writable: true,
98
+ value: false
99
+ });
100
+ this.on('error', (err) => {
101
+ console.error('[SSEEventCollectorTransform] Stream error:', err);
102
+ this.errorEmitted = true;
103
+ });
69
104
  }
70
105
  _transform(chunk, _encoding, callback) {
71
- this.buffer += chunk.toString('utf8');
72
- this.processBuffer();
73
- // 将chunk传递给下一个stream
74
- this.push(chunk);
75
- callback();
106
+ if (this.errorEmitted) {
107
+ callback();
108
+ return;
109
+ }
110
+ try {
111
+ // 如果是对象(来自 SSEParserTransform),先转换为字符串格式进行处理
112
+ if (typeof chunk === 'object' && chunk !== null) {
113
+ const sseEvent = chunk;
114
+ const lines = [];
115
+ if (sseEvent.event)
116
+ lines.push(`event: ${sseEvent.event}`);
117
+ if (sseEvent.id)
118
+ lines.push(`id: ${sseEvent.id}`);
119
+ if (sseEvent.data !== undefined) {
120
+ if (typeof sseEvent.data === 'string') {
121
+ lines.push(`data: ${sseEvent.data}`);
122
+ }
123
+ else {
124
+ lines.push(`data: ${JSON.stringify(sseEvent.data)}`);
125
+ }
126
+ }
127
+ if (lines.length > 0) {
128
+ this.currentEvent.rawLines.push(...lines);
129
+ if (sseEvent.event)
130
+ this.currentEvent.event = sseEvent.event;
131
+ if (sseEvent.id)
132
+ this.currentEvent.id = sseEvent.id;
133
+ if (sseEvent.data !== undefined) {
134
+ const dataStr = typeof sseEvent.data === 'string' ? sseEvent.data : JSON.stringify(sseEvent.data);
135
+ this.currentEvent.dataLines.push(dataStr);
136
+ }
137
+ this.flushEvent();
138
+ }
139
+ // 将原始对象传递给下一个stream
140
+ this.push(chunk);
141
+ }
142
+ else {
143
+ // Buffer/string 模式
144
+ this.buffer += chunk.toString('utf8');
145
+ this.processBuffer();
146
+ // 将chunk传递给下一个stream
147
+ this.push(chunk);
148
+ }
149
+ callback();
150
+ }
151
+ catch (error) {
152
+ console.error('[SSEEventCollectorTransform] Error in _transform:', error);
153
+ callback();
154
+ }
76
155
  }
77
156
  _flush(callback) {
78
- // 处理剩余的buffer
79
- if (this.buffer.trim()) {
80
- this.processBuffer();
157
+ try {
158
+ // 处理剩余的buffer
159
+ if (this.buffer.trim()) {
160
+ this.processBuffer();
161
+ }
162
+ // 刷新最后一个事件
163
+ this.flushEvent();
164
+ callback();
165
+ }
166
+ catch (error) {
167
+ console.error('[SSEEventCollectorTransform] Error in _flush:', error);
168
+ callback();
81
169
  }
82
- // 刷新最后一个事件
83
- this.flushEvent();
84
- callback();
85
170
  }
86
171
  processBuffer() {
87
172
  const lines = this.buffer.split('\n');
@@ -143,7 +143,9 @@ const transformClaudeRequestToOpenAIChat = (body, targetModel) => {
143
143
  // 映射 system 角色到 developer (如果需要)
144
144
  const mappedRole = (message.role === 'system' && useDeveloperRole) ? 'developer' : message.role;
145
145
  if (typeof message.content === 'string' || message.content === null) {
146
- messages.push({ role: mappedRole, content: message.content });
146
+ // 处理 content null 的情况,使用空字符串替代
147
+ const content = message.content === null ? '' : message.content;
148
+ messages.push({ role: mappedRole, content });
147
149
  continue;
148
150
  }
149
151
  if (Array.isArray(message.content)) {
@@ -179,7 +181,8 @@ const transformClaudeRequestToOpenAIChat = (body, targetModel) => {
179
181
  }
180
182
  }
181
183
  }
182
- const content = textParts.length > 0 ? textParts.join('') : null;
184
+ // 避免 content null,使用空字符串替代
185
+ const content = textParts.length > 0 ? textParts.join('') : '';
183
186
  const openaiMessage = {
184
187
  role: mappedRole,
185
188
  content,
@@ -28,22 +28,50 @@ class SSEParserTransform extends stream_1.Transform {
28
28
  writable: true,
29
29
  value: []
30
30
  });
31
+ Object.defineProperty(this, "errorEmitted", {
32
+ enumerable: true,
33
+ configurable: true,
34
+ writable: true,
35
+ value: false
36
+ });
37
+ // 捕获流中的未处理错误,防止进程崩溃
38
+ this.on('error', (err) => {
39
+ console.error('[SSEParserTransform] Stream error:', err);
40
+ this.errorEmitted = true;
41
+ });
31
42
  }
32
43
  _transform(chunk, _encoding, callback) {
33
- this.buffer += chunk.toString('utf8');
34
- const lines = this.buffer.split('\n');
35
- this.buffer = lines.pop() || '';
36
- for (const line of lines) {
37
- this.processLine(line);
44
+ if (this.errorEmitted) {
45
+ callback();
46
+ return;
47
+ }
48
+ try {
49
+ this.buffer += chunk.toString('utf8');
50
+ const lines = this.buffer.split('\n');
51
+ this.buffer = lines.pop() || '';
52
+ for (const line of lines) {
53
+ this.processLine(line);
54
+ }
55
+ callback();
56
+ }
57
+ catch (error) {
58
+ console.error('[SSEParserTransform] Error in _transform:', error);
59
+ // 不传递错误,避免中断流,而是记录并继续
60
+ callback();
38
61
  }
39
- callback();
40
62
  }
41
63
  _flush(callback) {
42
- if (this.buffer.trim()) {
43
- this.processLine(this.buffer.trim());
44
- this.flushEvent();
64
+ try {
65
+ if (this.buffer.trim()) {
66
+ this.processLine(this.buffer.trim());
67
+ this.flushEvent();
68
+ }
69
+ callback();
70
+ }
71
+ catch (error) {
72
+ console.error('[SSEParserTransform] Error in _flush:', error);
73
+ callback();
45
74
  }
46
- callback();
47
75
  }
48
76
  processLine(line) {
49
77
  if (!line.trim()) {
@@ -92,30 +120,50 @@ class SSESerializerTransform extends stream_1.Transform {
92
120
  writableObjectMode: true, // 接收对象
93
121
  readableObjectMode: false, // 输出字符串/Buffer
94
122
  });
123
+ Object.defineProperty(this, "errorEmitted", {
124
+ enumerable: true,
125
+ configurable: true,
126
+ writable: true,
127
+ value: false
128
+ });
129
+ this.on('error', (err) => {
130
+ console.error('[SSESerializerTransform] Stream error:', err);
131
+ this.errorEmitted = true;
132
+ });
95
133
  }
96
134
  _transform(event, _encoding, callback) {
97
135
  var _a;
98
- let output = '';
99
- if (event.event) {
100
- output += `event: ${event.event}\n`;
101
- }
102
- if (event.id) {
103
- output += `id: ${event.id}\n`;
136
+ if (this.errorEmitted) {
137
+ callback();
138
+ return;
104
139
  }
105
- if (event.data !== undefined) {
106
- if (((_a = event.data) === null || _a === void 0 ? void 0 : _a.type) === 'done') {
107
- output += 'data: [DONE]\n';
140
+ try {
141
+ let output = '';
142
+ if (event.event) {
143
+ output += `event: ${event.event}\n`;
108
144
  }
109
- else if (typeof event.data === 'string') {
110
- output += `data: ${event.data}\n`;
145
+ if (event.id) {
146
+ output += `id: ${event.id}\n`;
111
147
  }
112
- else {
113
- output += `data: ${JSON.stringify(event.data)}\n`;
148
+ if (event.data !== undefined) {
149
+ if (((_a = event.data) === null || _a === void 0 ? void 0 : _a.type) === 'done') {
150
+ output += 'data: [DONE]\n';
151
+ }
152
+ else if (typeof event.data === 'string') {
153
+ output += `data: ${event.data}\n`;
154
+ }
155
+ else {
156
+ output += `data: ${JSON.stringify(event.data)}\n`;
157
+ }
114
158
  }
159
+ output += '\n';
160
+ this.push(output);
161
+ callback();
162
+ }
163
+ catch (error) {
164
+ console.error('[SSESerializerTransform] Error in _transform:', error);
165
+ callback();
115
166
  }
116
- output += '\n';
117
- this.push(output);
118
- callback();
119
167
  }
120
168
  }
121
169
  exports.SSESerializerTransform = SSESerializerTransform;
@@ -206,7 +254,17 @@ class OpenAIToClaudeEventTransform extends stream_1.Transform {
206
254
  writable: true,
207
255
  value: false
208
256
  });
257
+ Object.defineProperty(this, "errorEmitted", {
258
+ enumerable: true,
259
+ configurable: true,
260
+ writable: true,
261
+ value: false
262
+ });
209
263
  this.model = (_a = options === null || options === void 0 ? void 0 : options.model) !== null && _a !== void 0 ? _a : null;
264
+ this.on('error', (err) => {
265
+ console.error('[OpenAIToClaudeEventTransform] Stream error:', err);
266
+ this.errorEmitted = true;
267
+ });
210
268
  }
211
269
  getUsage() {
212
270
  if (!this.usage)
@@ -215,39 +273,62 @@ class OpenAIToClaudeEventTransform extends stream_1.Transform {
215
273
  }
216
274
  _transform(event, _encoding, callback) {
217
275
  var _a;
218
- if (this.finalized) {
276
+ if (this.errorEmitted) {
219
277
  callback();
220
278
  return;
221
279
  }
222
- if (((_a = event.data) === null || _a === void 0 ? void 0 : _a.type) === 'done') {
223
- this.finalize();
224
- callback();
225
- return;
226
- }
227
- const chunk = event.data;
228
- if (!chunk) {
280
+ try {
281
+ if (this.finalized) {
282
+ callback();
283
+ return;
284
+ }
285
+ if (((_a = event.data) === null || _a === void 0 ? void 0 : _a.type) === 'done') {
286
+ this.finalize();
287
+ callback();
288
+ return;
289
+ }
290
+ const chunk = event.data;
291
+ if (!chunk) {
292
+ callback();
293
+ return;
294
+ }
295
+ if (chunk.id && !this.messageId) {
296
+ this.messageId = chunk.id;
297
+ }
298
+ if (chunk.model && !this.model) {
299
+ this.model = chunk.model;
300
+ }
301
+ if (chunk.usage) {
302
+ this.usage = (0, claude_openai_1.convertOpenAIUsageToClaude)(chunk.usage);
303
+ }
304
+ if (Array.isArray(chunk.choices)) {
305
+ for (const choice of chunk.choices) {
306
+ this.handleChoice(choice);
307
+ }
308
+ }
229
309
  callback();
230
- return;
231
- }
232
- if (chunk.id && !this.messageId) {
233
- this.messageId = chunk.id;
234
310
  }
235
- if (chunk.model && !this.model) {
236
- this.model = chunk.model;
237
- }
238
- if (chunk.usage) {
239
- this.usage = (0, claude_openai_1.convertOpenAIUsageToClaude)(chunk.usage);
240
- }
241
- if (Array.isArray(chunk.choices)) {
242
- for (const choice of chunk.choices) {
243
- this.handleChoice(choice);
311
+ catch (error) {
312
+ console.error('[OpenAIToClaudeEventTransform] Error in _transform:', error);
313
+ // 发送错误事件后继续
314
+ try {
315
+ this.pushEvent('error', { type: 'error', error: { type: 'api_error', message: 'Stream transformation error' } });
316
+ }
317
+ catch (e) {
318
+ // 忽略推送错误的错误
244
319
  }
320
+ callback();
245
321
  }
246
- callback();
247
322
  }
248
323
  _flush(callback) {
249
- this.finalize();
250
- callback();
324
+ try {
325
+ this.finalize();
326
+ callback();
327
+ }
328
+ catch (error) {
329
+ console.error('[OpenAIToClaudeEventTransform] Error in _flush:', error);
330
+ callback();
331
+ }
251
332
  }
252
333
  assignContentBlockIndex() {
253
334
  const index = this.contentIndex;
@@ -455,92 +536,118 @@ class ClaudeToOpenAIChatEventTransform extends stream_1.Transform {
455
536
  writable: true,
456
537
  value: null
457
538
  });
539
+ Object.defineProperty(this, "errorEmitted", {
540
+ enumerable: true,
541
+ configurable: true,
542
+ writable: true,
543
+ value: false
544
+ });
458
545
  this.model = (_a = options === null || options === void 0 ? void 0 : options.model) !== null && _a !== void 0 ? _a : null;
546
+ this.on('error', (err) => {
547
+ console.error('[ClaudeToOpenAIChatEventTransform] Stream error:', err);
548
+ this.errorEmitted = true;
549
+ });
459
550
  }
460
551
  getUsage() {
461
552
  return this.usage;
462
553
  }
463
554
  _transform(event, _encoding, callback) {
464
- if (this.finished) {
465
- callback();
466
- return;
467
- }
468
- const type = event.event;
469
- const data = event.data;
470
- if (type === 'message_start' && (data === null || data === void 0 ? void 0 : data.message)) {
471
- this.model = data.message.model || this.model;
472
- this.push({ event: null, data: { id: data.message.id, model: this.model, choices: [{ index: 0, delta: { role: 'assistant' }, finish_reason: null }] } });
555
+ if (this.errorEmitted) {
473
556
  callback();
474
557
  return;
475
558
  }
476
- if (type === 'content_block_start' && (data === null || data === void 0 ? void 0 : data.content_block)) {
477
- if (data.content_block.type === 'text') {
478
- // 文本块开始
559
+ try {
560
+ if (this.finished) {
561
+ callback();
562
+ return;
479
563
  }
480
- else if (data.content_block.type === 'tool_use') {
481
- this.pendingToolCallId = data.content_block.id;
482
- this.pendingToolName = data.content_block.name;
483
- this.pendingToolArgs = '';
564
+ const type = event.event;
565
+ const data = event.data;
566
+ if (type === 'message_start' && (data === null || data === void 0 ? void 0 : data.message)) {
567
+ this.model = data.message.model || this.model;
568
+ this.push({ event: null, data: { id: data.message.id, model: this.model, choices: [{ index: 0, delta: { role: 'assistant' }, finish_reason: null }] } });
569
+ callback();
570
+ return;
484
571
  }
485
- callback();
486
- return;
487
- }
488
- if (type === 'content_block_delta' && (data === null || data === void 0 ? void 0 : data.delta)) {
489
- if (data.delta.type === 'text_delta') {
490
- const text = data.delta.text || '';
491
- if (text) {
492
- this.push({ event: null, data: { id: '', model: this.model, choices: [{ index: 0, delta: { content: text } }] } });
572
+ if (type === 'content_block_start' && (data === null || data === void 0 ? void 0 : data.content_block)) {
573
+ if (data.content_block.type === 'text') {
574
+ // 文本块开始
493
575
  }
576
+ else if (data.content_block.type === 'tool_use') {
577
+ this.pendingToolCallId = data.content_block.id;
578
+ this.pendingToolName = data.content_block.name;
579
+ this.pendingToolArgs = '';
580
+ }
581
+ callback();
582
+ return;
494
583
  }
495
- else if (data.delta.type === 'input_json_delta') {
496
- this.pendingToolArgs += data.delta.partial_json || '';
584
+ if (type === 'content_block_delta' && (data === null || data === void 0 ? void 0 : data.delta)) {
585
+ if (data.delta.type === 'text_delta') {
586
+ const text = data.delta.text || '';
587
+ if (text) {
588
+ this.push({ event: null, data: { id: '', model: this.model, choices: [{ index: 0, delta: { content: text } }] } });
589
+ }
590
+ }
591
+ else if (data.delta.type === 'input_json_delta') {
592
+ this.pendingToolArgs += data.delta.partial_json || '';
593
+ }
594
+ callback();
595
+ return;
497
596
  }
498
- callback();
499
- return;
500
- }
501
- if (type === 'content_block_stop') {
502
- if (this.pendingToolCallId && this.pendingToolName !== null) {
503
- const toolCall = {
504
- index: this.toolCallIndex,
505
- id: this.pendingToolCallId,
506
- type: 'function',
507
- function: {
508
- name: this.pendingToolName,
509
- arguments: this.pendingToolArgs,
510
- },
597
+ if (type === 'content_block_stop') {
598
+ if (this.pendingToolCallId && this.pendingToolName !== null) {
599
+ const toolCall = {
600
+ index: this.toolCallIndex,
601
+ id: this.pendingToolCallId,
602
+ type: 'function',
603
+ function: {
604
+ name: this.pendingToolName,
605
+ arguments: this.pendingToolArgs,
606
+ },
607
+ };
608
+ this.push({ event: null, data: { id: '', model: this.model, choices: [{ index: 0, delta: { tool_calls: [toolCall] } }] } });
609
+ this.toolCallIndex++;
610
+ this.pendingToolCallId = null;
611
+ this.pendingToolName = null;
612
+ this.pendingToolArgs = '';
613
+ }
614
+ callback();
615
+ return;
616
+ }
617
+ if (type === 'message_stop' || (data === null || data === void 0 ? void 0 : data.type) === 'message_stop') {
618
+ this.finished = true;
619
+ this.push({ event: null, data: { id: '', model: this.model, choices: [{ index: 0, delta: {}, finish_reason: 'stop' }] } });
620
+ this.push({ event: 'done', data: { type: 'done' } });
621
+ callback();
622
+ return;
623
+ }
624
+ if (data === null || data === void 0 ? void 0 : data.usage) {
625
+ this.usage = {
626
+ prompt_tokens: data.usage.input_tokens || 0,
627
+ completion_tokens: data.usage.output_tokens || 0,
628
+ total_tokens: (data.usage.input_tokens || 0) + (data.usage.output_tokens || 0),
511
629
  };
512
- this.push({ event: null, data: { id: '', model: this.model, choices: [{ index: 0, delta: { tool_calls: [toolCall] } }] } });
513
- this.toolCallIndex++;
514
- this.pendingToolCallId = null;
515
- this.pendingToolName = null;
516
- this.pendingToolArgs = '';
517
630
  }
518
631
  callback();
519
- return;
520
632
  }
521
- if (type === 'message_stop' || (data === null || data === void 0 ? void 0 : data.type) === 'message_stop') {
522
- this.finished = true;
523
- this.push({ event: null, data: { id: '', model: this.model, choices: [{ index: 0, delta: {}, finish_reason: 'stop' }] } });
524
- this.push({ event: 'done', data: { type: 'done' } });
633
+ catch (error) {
634
+ console.error('[ClaudeToOpenAIChatEventTransform] Error in _transform:', error);
525
635
  callback();
526
- return;
527
- }
528
- if (data === null || data === void 0 ? void 0 : data.usage) {
529
- this.usage = {
530
- prompt_tokens: data.usage.input_tokens || 0,
531
- completion_tokens: data.usage.output_tokens || 0,
532
- total_tokens: (data.usage.input_tokens || 0) + (data.usage.output_tokens || 0),
533
- };
534
636
  }
535
- callback();
536
637
  }
537
638
  _flush(callback) {
538
- if (!this.finished) {
539
- this.finished = true;
540
- this.push({ event: null, data: { id: '', model: this.model, choices: [{ index: 0, delta: {}, finish_reason: 'stop' }] } });
541
- this.push({ event: 'done', data: { type: 'done' } });
639
+ try {
640
+ if (!this.finished) {
641
+ this.finished = true;
642
+ this.push({ event: null, data: { id: '', model: this.model, choices: [{ index: 0, delta: {}, finish_reason: 'stop' }] } });
643
+ this.push({ event: 'done', data: { type: 'done' } });
644
+ }
645
+ callback();
646
+ }
647
+ catch (error) {
648
+ console.error('[ClaudeToOpenAIChatEventTransform] Error in _flush:', error);
649
+ callback();
542
650
  }
543
- callback();
544
651
  }
545
652
  }
546
653
  exports.ClaudeToOpenAIChatEventTransform = ClaudeToOpenAIChatEventTransform;