protocol-proxy 2.3.2 → 2.3.4

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.
@@ -133,12 +133,14 @@ function mapFinishReason(reason) {
133
133
  // ==================== SSE 流式转换 ====================
134
134
 
135
135
  function createSSEConverter() {
136
- const state = { started: false, textBlockStarted: false, textBlockClosed: false, blockIndex: 0, sentFunctionCall: new Map() };
136
+ const state = { started: false, textBlockStarted: false, textBlockClosed: false, blockIndex: 0, sentFunctionCall: new Map(), buffer: '' };
137
137
 
138
138
  return {
139
139
  convertChunk(chunkText) {
140
140
  let output = '';
141
- const lines = chunkText.split('\n');
141
+ state.buffer += chunkText;
142
+ const lines = state.buffer.split('\n');
143
+ state.buffer = lines.pop() || '';
142
144
 
143
145
  for (const line of lines) {
144
146
  const trimmed = line.trim();
@@ -218,7 +218,10 @@ function createSSEConverter(targetModel) {
218
218
  messageId: null,
219
219
  started: false,
220
220
  textBlockStarted: false,
221
- toolCalls: new Map(), // index -> { id, name, args }
221
+ textBlockClosed: false,
222
+ textBlockIndex: 0,
223
+ blockIndex: 0,
224
+ toolCalls: new Map(), // index -> { id, name, args, blockIndex }
222
225
  buffer: '',
223
226
  };
224
227
 
@@ -292,32 +295,41 @@ function processLine(line, state, targetModel) {
292
295
  if (delta.content !== undefined && delta.content !== null) {
293
296
  if (!state.textBlockStarted) {
294
297
  state.textBlockStarted = true;
298
+ state.textBlockIndex = state.blockIndex++;
295
299
  output += encodeAnthropicEvent('content_block_start', {
296
300
  type: 'content_block_start',
297
- index: 0,
301
+ index: state.textBlockIndex,
298
302
  content_block: { type: 'text', text: '' },
299
303
  });
300
304
  }
301
305
  output += encodeAnthropicEvent('content_block_delta', {
302
306
  type: 'content_block_delta',
303
- index: 0,
307
+ index: state.textBlockIndex,
304
308
  delta: { type: 'text_delta', text: delta.content },
305
309
  });
306
310
  }
307
311
 
308
312
  // tool_calls
309
313
  if (delta.tool_calls && Array.isArray(delta.tool_calls)) {
314
+ // 在首个 tool_call 前关闭 text block
315
+ if (state.textBlockStarted && !state.textBlockClosed) {
316
+ state.textBlockClosed = true;
317
+ output += encodeAnthropicEvent('content_block_stop', {
318
+ type: 'content_block_stop',
319
+ index: state.textBlockIndex,
320
+ });
321
+ }
310
322
  for (const tc of delta.tool_calls) {
311
323
  const idx = tc.index || 0;
312
324
  let tool = state.toolCalls.get(idx);
313
325
 
314
326
  if (!tool) {
315
327
  // 新的 tool_call
316
- tool = { id: tc.id, name: tc.function?.name, args: '' };
328
+ tool = { id: tc.id, name: tc.function?.name, args: '', blockIndex: state.blockIndex++ };
317
329
  state.toolCalls.set(idx, tool);
318
330
  output += encodeAnthropicEvent('content_block_start', {
319
331
  type: 'content_block_start',
320
- index: idx + 1, // text block 占 index 0
332
+ index: tool.blockIndex,
321
333
  content_block: {
322
334
  type: 'tool_use',
323
335
  id: tc.id,
@@ -331,7 +343,7 @@ function processLine(line, state, targetModel) {
331
343
  tool.args += tc.function.arguments;
332
344
  output += encodeAnthropicEvent('content_block_delta', {
333
345
  type: 'content_block_delta',
334
- index: idx + 1,
346
+ index: tool.blockIndex,
335
347
  delta: { type: 'input_json_delta', partial_json: tc.function.arguments },
336
348
  });
337
349
  }
@@ -341,16 +353,17 @@ function processLine(line, state, targetModel) {
341
353
  // finish_reason
342
354
  if (choice.finish_reason) {
343
355
  const stopReason = mapFinishReason(choice.finish_reason);
344
- if (state.textBlockStarted) {
356
+ if (state.textBlockStarted && !state.textBlockClosed) {
357
+ state.textBlockClosed = true;
345
358
  output += encodeAnthropicEvent('content_block_stop', {
346
359
  type: 'content_block_stop',
347
- index: 0,
360
+ index: state.textBlockIndex,
348
361
  });
349
362
  }
350
- for (let i = 0; i < state.toolCalls.size; i++) {
363
+ for (const [, tool] of state.toolCalls) {
351
364
  output += encodeAnthropicEvent('content_block_stop', {
352
365
  type: 'content_block_stop',
353
- index: i + 1,
366
+ index: tool.blockIndex,
354
367
  });
355
368
  }
356
369
  output += encodeAnthropicEvent('message_delta', {
@@ -155,12 +155,14 @@ function mapFinishReason(reason) {
155
155
  // ==================== SSE 流式转换 ====================
156
156
 
157
157
  function createSSEConverter(nameToId = new Map()) {
158
- const state = { started: false, textBlockStarted: false, textBlockClosed: false, blockIndex: 0, sentFunctionCall: new Map(), nameToId };
158
+ const state = { started: false, textBlockStarted: false, textBlockClosed: false, blockIndex: 0, sentFunctionCall: new Map(), nameToId, buffer: '' };
159
159
 
160
160
  return {
161
161
  convertChunk(chunkText) {
162
162
  let output = '';
163
- const lines = chunkText.split('\n');
163
+ state.buffer += chunkText;
164
+ const lines = state.buffer.split('\n');
165
+ state.buffer = lines.pop() || '';
164
166
 
165
167
  for (const line of lines) {
166
168
  const trimmed = line.trim();
@@ -152,12 +152,14 @@ function mapFinishReason(reason) {
152
152
  // ==================== SSE 流式转换 ====================
153
153
 
154
154
  function createSSEConverter() {
155
- const state = { started: false, sentFunctionCall: new Map() };
155
+ const state = { started: false, sentFunctionCall: new Map(), buffer: '' };
156
156
 
157
157
  return {
158
158
  convertChunk(chunkText) {
159
159
  let output = '';
160
- const lines = chunkText.split('\n');
160
+ state.buffer += chunkText;
161
+ const lines = state.buffer.split('\n');
162
+ state.buffer = lines.pop() || '';
161
163
 
162
164
  for (const line of lines) {
163
165
  const trimmed = line.trim();
@@ -188,6 +188,7 @@ function createSSEConverter(targetModel) {
188
188
  blockIndex: 0,
189
189
  toolUseId: null,
190
190
  toolName: null,
191
+ toolCallIndex: -1,
191
192
  sentRole: false,
192
193
  sentToolInit: false,
193
194
  buffer: '',
@@ -243,6 +244,7 @@ function processLine(line, state, targetModel) {
243
244
  if (state.blockType === 'tool_use') {
244
245
  state.toolUseId = event.content_block.id;
245
246
  state.toolName = event.content_block.name;
247
+ state.toolCallIndex = (state.toolCallIndex || 0) + 1;
246
248
  }
247
249
  return '';
248
250
  }
@@ -267,7 +269,7 @@ function processLine(line, state, targetModel) {
267
269
  state.sentToolInit = true;
268
270
  const toolCallChunk = {
269
271
  tool_calls: [{
270
- index: 0,
272
+ index: state.toolCallIndex,
271
273
  id: state.toolUseId,
272
274
  type: 'function',
273
275
  function: { name: state.toolName, arguments: delta.partial_json },
@@ -276,7 +278,7 @@ function processLine(line, state, targetModel) {
276
278
  return prefix + encodeOpenAIChunk(state.messageId, targetModel, toolCallChunk);
277
279
  }
278
280
  return prefix + encodeOpenAIChunk(state.messageId, targetModel, {
279
- tool_calls: [{ index: 0, function: { arguments: delta.partial_json } }],
281
+ tool_calls: [{ index: state.toolCallIndex, function: { arguments: delta.partial_json } }],
280
282
  });
281
283
  }
282
284
 
@@ -183,12 +183,14 @@ function mapFinishReason(reason) {
183
183
  // ==================== SSE 流式转换 ====================
184
184
 
185
185
  function createSSEConverter() {
186
- const state = { started: false, sentFunctionCall: new Map() };
186
+ const state = { started: false, sentFunctionCall: new Map(), buffer: '' };
187
187
 
188
188
  return {
189
189
  convertChunk(chunkText) {
190
190
  let output = '';
191
- const lines = chunkText.split('\n');
191
+ state.buffer += chunkText;
192
+ const lines = state.buffer.split('\n');
193
+ state.buffer = lines.pop() || '';
192
194
 
193
195
  for (const line of lines) {
194
196
  const trimmed = line.trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "protocol-proxy",
3
- "version": "2.3.2",
3
+ "version": "2.3.4",
4
4
  "description": "OpenAI / Anthropic 协议转换透明代理",
5
5
  "main": "server.js",
6
6
  "bin": {