@mmmbuto/zai-codex-bridge 0.3.0 → 0.3.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/server.js +115 -95
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.2",
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
@@ -12,6 +12,8 @@
12
12
 
13
13
  const http = require('http');
14
14
  const crypto = require('crypto');
15
+ const { createGunzip } = require('zlib');
16
+ const { pipeline } = require('stream');
15
17
 
16
18
  // Configuration from environment
17
19
  const PORT = parseInt(process.env.PORT || '31415', 10);
@@ -269,7 +271,8 @@ async function makeUpstreamRequest(path, body, headers) {
269
271
  const auth = pickAuth(headers);
270
272
  const upstreamHeaders = {
271
273
  'Content-Type': 'application/json',
272
- 'Authorization': auth
274
+ 'Authorization': auth,
275
+ 'Accept-Encoding': 'identity' // Disable compression to avoid gzip issues
273
276
  };
274
277
 
275
278
  log('info', 'Upstream request:', {
@@ -456,107 +459,124 @@ async function streamChatToResponses(stream, res, responseId, messageItemId) {
456
459
 
457
460
  try {
458
461
  for await (const chunk of stream) {
459
- buffer += chunk.toString('utf8');
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)}...`);
462
+ const chunkStr = Buffer.from(chunk).toString('utf8');
463
+ buffer += chunkStr;
464
+
465
+ // Z.ai stream: SSE lines "data: {...}\n"
466
+ // Split by newline and process each complete line
467
+ const lines = buffer.split('\n');
468
+ // Keep the last line if it's incomplete (doesn't end with data pattern)
469
+ buffer = lines.pop() || '';
470
+
471
+ for (const line of lines) {
472
+ if (!line.trim() || !line.startsWith('data:')) {
473
+ // Skip empty lines and comments (starting with :)
474
+ if (line.trim() && !line.startsWith(':')) {
475
+ log('debug', 'Non-data line:', line.substring(0, 50));
491
476
  }
477
+ continue;
478
+ }
492
479
 
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
- }
480
+ const payload = line.slice(5).trim();
481
+ if (payload === '[DONE]') {
482
+ log('info', 'Stream received [DONE]');
483
+ await finalizeAndClose();
484
+ return;
485
+ }
486
+
487
+ if (!payload) continue;
488
+
489
+ let json;
490
+ try {
491
+ json = JSON.parse(payload);
492
+ } catch (e) {
493
+ log('warn', 'Failed to parse SSE payload:', e.message, 'payload:', payload.substring(0, 100));
494
+ continue;
495
+ }
506
496
 
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
- }
497
+ const choice = json?.choices?.[0];
498
+ const delta = choice?.delta ?? {};
499
+
500
+ // 1) reasoning
501
+ if (typeof delta.reasoning_content === 'string' && delta.reasoning_content.length) {
502
+ reasoningText += delta.reasoning_content;
503
+ send({
504
+ type: 'response.reasoning_text.delta',
505
+ sequence_number: seq++,
506
+ item_id: messageItemId,
507
+ output_index: 0,
508
+ content_index: 0,
509
+ delta: delta.reasoning_content,
510
+ });
511
+ log('debug', `Reasoning delta: ${delta.reasoning_content.substring(0, 30)}...`);
512
+ }
513
+
514
+ // 2) normal output
515
+ if (typeof delta.content === 'string' && delta.content.length) {
516
+ outputText += delta.content;
517
+ send({
518
+ type: 'response.output_text.delta',
519
+ sequence_number: seq++,
520
+ item_id: messageItemId,
521
+ output_index: 0,
522
+ content_index: reasoningText ? 1 : 0,
523
+ delta: delta.content,
524
+ });
525
+ log('debug', `Output delta: ${delta.content.substring(0, 30)}...`);
526
+ }
527
+
528
+ // 3) tool calls (OpenAI-style in chat.completions delta.tool_calls)
529
+ if (Array.isArray(delta.tool_calls)) {
530
+ for (const tc of delta.tool_calls) {
531
+ // tc: {id, type:"function", function:{name, arguments}}
532
+ const callId = tc.id || `call_${tc.index ?? 0}`;
533
+ const name = tc.function?.name || 'unknown';
534
+ const argsDelta = tc.function?.arguments || '';
535
+
536
+ let st = toolCalls.get(callId);
537
+ if (!st) {
538
+ st = {
539
+ itemId: `fc_${crypto.randomUUID().replace(/-/g, '')}`,
540
+ outputIndex: nextOutputIndex++,
541
+ name,
542
+ args: '',
543
+ };
544
+ toolCalls.set(callId, st);
545
+
546
+ send({
547
+ type: 'response.output_item.added',
548
+ sequence_number: seq++,
549
+ output_index: st.outputIndex,
550
+ item: {
551
+ type: 'function_call',
552
+ id: st.itemId,
553
+ call_id: callId,
554
+ name: st.name,
555
+ arguments: '',
556
+ },
557
+ });
558
+ log('debug', `Tool call added: ${name} (${callId})`);
550
559
  }
551
- }
552
560
 
553
- // 4) finish
554
- if (choice?.finish_reason) {
555
- log('info', `Stream finish_reason: ${choice.finish_reason}`);
556
- await finalizeAndClose();
557
- return;
561
+ if (argsDelta) {
562
+ st.args += argsDelta;
563
+ send({
564
+ type: 'response.function_call_arguments.delta',
565
+ sequence_number: seq++,
566
+ item_id: st.itemId,
567
+ output_index: st.outputIndex,
568
+ delta: argsDelta,
569
+ });
570
+ }
558
571
  }
559
572
  }
573
+
574
+ // 4) finish
575
+ if (choice?.finish_reason) {
576
+ log('info', `Stream finish_reason: ${choice.finish_reason}`);
577
+ await finalizeAndClose();
578
+ return;
579
+ }
560
580
  }
561
581
  }
562
582
  } catch (e) {