@mmmbuto/zai-codex-bridge 0.1.11 → 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 -26
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmmbuto/zai-codex-bridge",
3
- "version": "0.1.11",
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,24 +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
- log('info', 'Sending response.completed event:', JSON.stringify(completedEvent));
306
- res.write(`event: response.completed\n`);
327
+ log('info', 'Sending response.completed event');
307
328
  res.write(`data: ${JSON.stringify(completedEvent)}\n\n`);
308
329
  log('info', 'Sent response.completed event');
309
330
  return;
@@ -311,26 +332,31 @@ async function streamChatToResponses(stream, res) {
311
332
 
312
333
  try {
313
334
  const parsed = JSON.parse(data);
314
- lastParsed = parsed; // Save for later use in completed event
335
+ lastParsed = parsed;
315
336
  log('debug', 'Parsed SSE:', JSON.stringify(parsed).substring(0, 150));
316
337
 
317
338
  const delta = parsed.choices?.[0]?.delta;
318
-
319
- // Z.AI uses reasoning_content instead of content
320
339
  const content = delta?.content || delta?.reasoning_content || '';
321
340
 
322
341
  if (content) {
323
342
  deltaCount++;
324
343
  log('debug', 'Writing delta:', content.substring(0, 30));
325
- res.write(`event: output.text.delta\n`);
326
- 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`);
327
353
  }
328
354
  } catch (e) {
329
355
  log('warn', 'Failed to parse SSE chunk:', e.message, 'data:', data.substring(0, 100));
330
356
  }
331
357
  }
332
358
 
333
- if (chunkCount > 100) {
359
+ if (chunkCount > 1000) {
334
360
  log('warn', 'Too many chunks, possible loop');
335
361
  return;
336
362
  }
@@ -413,6 +439,7 @@ async function handlePostRequest(req, res) {
413
439
 
414
440
  // Handle streaming response
415
441
  if (upstreamBody.stream) {
442
+ const responseId = 'resp_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
416
443
  log('info', 'Starting streaming response');
417
444
  res.writeHead(200, {
418
445
  'Content-Type': 'text/event-stream; charset=utf-8',
@@ -421,7 +448,7 @@ async function handlePostRequest(req, res) {
421
448
  });
422
449
 
423
450
  try {
424
- await streamChatToResponses(upstreamResponse.body, res);
451
+ await streamChatToResponses(upstreamResponse.body, res, responseId);
425
452
  log('info', 'Streaming completed');
426
453
  } catch (e) {
427
454
  log('error', 'Streaming error:', e);