@mmmbuto/zai-codex-bridge 0.1.10 → 0.1.12

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 +53 -25
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmmbuto/zai-codex-bridge",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
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
@@ -199,16 +199,28 @@ function translateChatToResponses(chatResponse) {
199
199
  }
200
200
  }
201
201
 
202
+ const responseId = 'resp_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
203
+
204
+ // OpenAI Responses API format
202
205
  const response = {
206
+ id: responseId,
207
+ object: 'response',
208
+ created_at: Math.floor(Date.now() / 1000),
209
+ status: 'completed',
210
+ model: chatResponse.model || 'glm-4.7',
203
211
  output: [{
204
- value: text,
205
- content_type: 'text'
212
+ type: 'message',
213
+ role: 'assistant',
214
+ content: [{
215
+ type: 'output_text',
216
+ text: text
217
+ }]
206
218
  }],
207
- status: 'completed',
208
219
  usage: Object.keys(usage).length > 0 ? usage : undefined
209
220
  };
210
221
 
211
222
  log('debug', 'Translated Chat->Responses:', {
223
+ id: response.id,
212
224
  outputLength: text.length,
213
225
  status: response.status
214
226
  });
@@ -255,12 +267,13 @@ async function makeUpstreamRequest(path, body, headers) {
255
267
  /**
256
268
  * Handle streaming response from Z.AI
257
269
  */
258
- async function streamChatToResponses(stream, res) {
270
+ async function streamChatToResponses(stream, res, responseId) {
259
271
  const decoder = new TextDecoder();
260
272
  let buffer = '';
261
273
  let chunkCount = 0;
262
274
  let deltaCount = 0;
263
- let lastParsed = null; // Keep track of last parsed SSE for ID extraction
275
+ let lastParsed = null;
276
+ const itemId = 'item_' + Date.now();
264
277
 
265
278
  log('debug', 'Starting to process stream');
266
279
 
@@ -286,23 +299,32 @@ async function streamChatToResponses(stream, res) {
286
299
  if (data === '[DONE]') {
287
300
  log('info', `Stream end received - wrote ${deltaCount} deltas total`);
288
301
 
289
- // Send response.completed event (required by Codex Responses API)
290
- // Map Z.AI usage format to Responses API format
302
+ // Send response.completed event in OpenAI Responses API format
291
303
  const zaiUsage = lastParsed?.usage;
292
304
  const completedEvent = {
293
- id: lastParsed?.id || 'msg_' + Date.now(),
294
- usage: zaiUsage ? {
295
- input_tokens: zaiUsage.prompt_tokens || 0,
296
- output_tokens: zaiUsage.completion_tokens || 0,
297
- total_tokens: zaiUsage.total_tokens || 0
298
- } : {
299
- input_tokens: 0,
300
- output_tokens: 0,
301
- total_tokens: 0
302
- }
305
+ type: 'response.completed',
306
+ response: {
307
+ id: responseId,
308
+ status: 'completed',
309
+ output: [{
310
+ type: 'message',
311
+ role: 'assistant',
312
+ content: [{ type: 'output_text', text: '' }]
313
+ }],
314
+ usage: zaiUsage ? {
315
+ input_tokens: zaiUsage.prompt_tokens || 0,
316
+ output_tokens: zaiUsage.completion_tokens || 0,
317
+ total_tokens: zaiUsage.total_tokens || 0
318
+ } : {
319
+ input_tokens: 0,
320
+ output_tokens: 0,
321
+ total_tokens: 0
322
+ }
323
+ },
324
+ sequence_number: deltaCount
303
325
  };
304
326
 
305
- res.write(`event: response.completed\n`);
327
+ log('info', 'Sending response.completed event');
306
328
  res.write(`data: ${JSON.stringify(completedEvent)}\n\n`);
307
329
  log('info', 'Sent response.completed event');
308
330
  return;
@@ -310,26 +332,31 @@ async function streamChatToResponses(stream, res) {
310
332
 
311
333
  try {
312
334
  const parsed = JSON.parse(data);
313
- lastParsed = parsed; // Save for later use in completed event
335
+ lastParsed = parsed;
314
336
  log('debug', 'Parsed SSE:', JSON.stringify(parsed).substring(0, 150));
315
337
 
316
338
  const delta = parsed.choices?.[0]?.delta;
317
-
318
- // Z.AI uses reasoning_content instead of content
319
339
  const content = delta?.content || delta?.reasoning_content || '';
320
340
 
321
341
  if (content) {
322
342
  deltaCount++;
323
343
  log('debug', 'Writing delta:', content.substring(0, 30));
324
- res.write(`event: output.text.delta\n`);
325
- res.write(`data: ${JSON.stringify({ value: content })}\n\n`);
344
+ // OpenAI Responses API format for text delta
345
+ const deltaEvent = {
346
+ type: 'response.output_text.delta',
347
+ delta: content,
348
+ output_index: 0,
349
+ item_id: itemId,
350
+ sequence_number: deltaCount - 1
351
+ };
352
+ res.write(`data: ${JSON.stringify(deltaEvent)}\n\n`);
326
353
  }
327
354
  } catch (e) {
328
355
  log('warn', 'Failed to parse SSE chunk:', e.message, 'data:', data.substring(0, 100));
329
356
  }
330
357
  }
331
358
 
332
- if (chunkCount > 100) {
359
+ if (chunkCount > 1000) {
333
360
  log('warn', 'Too many chunks, possible loop');
334
361
  return;
335
362
  }
@@ -412,6 +439,7 @@ async function handlePostRequest(req, res) {
412
439
 
413
440
  // Handle streaming response
414
441
  if (upstreamBody.stream) {
442
+ const responseId = 'resp_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
415
443
  log('info', 'Starting streaming response');
416
444
  res.writeHead(200, {
417
445
  'Content-Type': 'text/event-stream; charset=utf-8',
@@ -420,7 +448,7 @@ async function handlePostRequest(req, res) {
420
448
  });
421
449
 
422
450
  try {
423
- await streamChatToResponses(upstreamResponse.body, res);
451
+ await streamChatToResponses(upstreamResponse.body, res, responseId);
424
452
  log('info', 'Streaming completed');
425
453
  } catch (e) {
426
454
  log('error', 'Streaming error:', e);