@mmmbuto/zai-codex-bridge 0.3.0 → 0.3.1

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/server.js +108 -92
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmmbuto/zai-codex-bridge",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Local proxy that translates OpenAI Responses API format to Z.AI Chat Completions format for Codex",
5
5
  "main": "src/server.js",
6
6
  "bin": {
package/src/server.js CHANGED
@@ -458,105 +458,121 @@ async function streamChatToResponses(stream, res, responseId, messageItemId) {
458
458
  for await (const chunk of stream) {
459
459
  buffer += chunk.toString('utf8');
460
460
 
461
- // Z.ai stream: SSE lines "data: {...}\n\n"
462
- let idx;
463
- while ((idx = buffer.indexOf('\n\n')) !== -1) {
464
- const raw = buffer.slice(0, idx);
465
- buffer = buffer.slice(idx + 2);
466
-
467
- const lines = raw.split('\n');
468
- for (const line of lines) {
469
- if (!line.startsWith('data:')) continue;
470
- const payload = line.slice(5).trim();
471
- if (!payload || payload === '[DONE]') continue;
472
-
473
- let json;
474
- try { json = JSON.parse(payload); } catch { continue; }
475
-
476
- const choice = json?.choices?.[0];
477
- const delta = choice?.delta ?? {};
478
-
479
- // 1) reasoning
480
- if (typeof delta.reasoning_content === 'string' && delta.reasoning_content.length) {
481
- reasoningText += delta.reasoning_content;
482
- send({
483
- type: 'response.reasoning_text.delta',
484
- sequence_number: seq++,
485
- item_id: messageItemId,
486
- output_index: 0,
487
- content_index: 0,
488
- delta: delta.reasoning_content,
489
- });
490
- log('debug', `Reasoning delta: ${delta.reasoning_content.substring(0, 30)}...`);
461
+ // Z.ai stream: SSE lines "data: {...}\n"
462
+ // Split by newline and process each complete line
463
+ const lines = buffer.split('\n');
464
+ // Keep the last line if it's incomplete (doesn't end with data pattern)
465
+ buffer = lines.pop() || '';
466
+
467
+ for (const line of lines) {
468
+ if (!line.trim() || !line.startsWith('data:')) {
469
+ // Skip empty lines and comments (starting with :)
470
+ if (line.trim() && !line.startsWith(':')) {
471
+ log('debug', 'Non-data line:', line.substring(0, 50));
491
472
  }
473
+ continue;
474
+ }
492
475
 
493
- // 2) normal output
494
- if (typeof delta.content === 'string' && delta.content.length) {
495
- outputText += delta.content;
496
- send({
497
- type: 'response.output_text.delta',
498
- sequence_number: seq++,
499
- item_id: messageItemId,
500
- output_index: 0,
501
- content_index: reasoningText ? 1 : 0,
502
- delta: delta.content,
503
- });
504
- log('debug', `Output delta: ${delta.content.substring(0, 30)}...`);
505
- }
476
+ const payload = line.slice(5).trim();
477
+ if (payload === '[DONE]') {
478
+ log('info', 'Stream received [DONE]');
479
+ await finalizeAndClose();
480
+ return;
481
+ }
482
+
483
+ if (!payload) continue;
484
+
485
+ let json;
486
+ try {
487
+ json = JSON.parse(payload);
488
+ } catch (e) {
489
+ log('warn', 'Failed to parse SSE payload:', e.message, 'payload:', payload.substring(0, 100));
490
+ continue;
491
+ }
506
492
 
507
- // 3) tool calls (OpenAI-style in chat.completions delta.tool_calls)
508
- if (Array.isArray(delta.tool_calls)) {
509
- for (const tc of delta.tool_calls) {
510
- // tc: {id, type:"function", function:{name, arguments}}
511
- const callId = tc.id || `call_${tc.index ?? 0}`;
512
- const name = tc.function?.name || 'unknown';
513
- const argsDelta = tc.function?.arguments || '';
514
-
515
- let st = toolCalls.get(callId);
516
- if (!st) {
517
- st = {
518
- itemId: `fc_${crypto.randomUUID().replace(/-/g, '')}`,
519
- outputIndex: nextOutputIndex++,
520
- name,
521
- args: '',
522
- };
523
- toolCalls.set(callId, st);
524
-
525
- send({
526
- type: 'response.output_item.added',
527
- sequence_number: seq++,
528
- output_index: st.outputIndex,
529
- item: {
530
- type: 'function_call',
531
- id: st.itemId,
532
- call_id: callId,
533
- name: st.name,
534
- arguments: '',
535
- },
536
- });
537
- log('debug', `Tool call added: ${name} (${callId})`);
538
- }
539
-
540
- if (argsDelta) {
541
- st.args += argsDelta;
542
- send({
543
- type: 'response.function_call_arguments.delta',
544
- sequence_number: seq++,
545
- item_id: st.itemId,
546
- output_index: st.outputIndex,
547
- delta: argsDelta,
548
- });
549
- }
493
+ const choice = json?.choices?.[0];
494
+ const delta = choice?.delta ?? {};
495
+
496
+ // 1) reasoning
497
+ if (typeof delta.reasoning_content === 'string' && delta.reasoning_content.length) {
498
+ reasoningText += delta.reasoning_content;
499
+ send({
500
+ type: 'response.reasoning_text.delta',
501
+ sequence_number: seq++,
502
+ item_id: messageItemId,
503
+ output_index: 0,
504
+ content_index: 0,
505
+ delta: delta.reasoning_content,
506
+ });
507
+ log('debug', `Reasoning delta: ${delta.reasoning_content.substring(0, 30)}...`);
508
+ }
509
+
510
+ // 2) normal output
511
+ if (typeof delta.content === 'string' && delta.content.length) {
512
+ outputText += delta.content;
513
+ send({
514
+ type: 'response.output_text.delta',
515
+ sequence_number: seq++,
516
+ item_id: messageItemId,
517
+ output_index: 0,
518
+ content_index: reasoningText ? 1 : 0,
519
+ delta: delta.content,
520
+ });
521
+ log('debug', `Output delta: ${delta.content.substring(0, 30)}...`);
522
+ }
523
+
524
+ // 3) tool calls (OpenAI-style in chat.completions delta.tool_calls)
525
+ if (Array.isArray(delta.tool_calls)) {
526
+ for (const tc of delta.tool_calls) {
527
+ // tc: {id, type:"function", function:{name, arguments}}
528
+ const callId = tc.id || `call_${tc.index ?? 0}`;
529
+ const name = tc.function?.name || 'unknown';
530
+ const argsDelta = tc.function?.arguments || '';
531
+
532
+ let st = toolCalls.get(callId);
533
+ if (!st) {
534
+ st = {
535
+ itemId: `fc_${crypto.randomUUID().replace(/-/g, '')}`,
536
+ outputIndex: nextOutputIndex++,
537
+ name,
538
+ args: '',
539
+ };
540
+ toolCalls.set(callId, st);
541
+
542
+ send({
543
+ type: 'response.output_item.added',
544
+ sequence_number: seq++,
545
+ output_index: st.outputIndex,
546
+ item: {
547
+ type: 'function_call',
548
+ id: st.itemId,
549
+ call_id: callId,
550
+ name: st.name,
551
+ arguments: '',
552
+ },
553
+ });
554
+ log('debug', `Tool call added: ${name} (${callId})`);
550
555
  }
551
- }
552
556
 
553
- // 4) finish
554
- if (choice?.finish_reason) {
555
- log('info', `Stream finish_reason: ${choice.finish_reason}`);
556
- await finalizeAndClose();
557
- return;
557
+ if (argsDelta) {
558
+ st.args += argsDelta;
559
+ send({
560
+ type: 'response.function_call_arguments.delta',
561
+ sequence_number: seq++,
562
+ item_id: st.itemId,
563
+ output_index: st.outputIndex,
564
+ delta: argsDelta,
565
+ });
566
+ }
558
567
  }
559
568
  }
569
+
570
+ // 4) finish
571
+ if (choice?.finish_reason) {
572
+ log('info', `Stream finish_reason: ${choice.finish_reason}`);
573
+ await finalizeAndClose();
574
+ return;
575
+ }
560
576
  }
561
577
  }
562
578
  } catch (e) {