@mmmbuto/zai-codex-bridge 0.1.11 → 0.1.13

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 +68 -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.13",
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,15 +267,30 @@ 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, itemId) {
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;
264
276
 
265
277
  log('debug', 'Starting to process stream');
266
278
 
279
+ // Send initial event to create the output item
280
+ const addEvent = {
281
+ type: 'response.output_item.add',
282
+ item: {
283
+ type: 'message',
284
+ role: 'assistant',
285
+ content: [{ type: 'output_text', text: '' }],
286
+ id: itemId
287
+ },
288
+ output_index: 0,
289
+ response_id: responseId
290
+ };
291
+ res.write(`data: ${JSON.stringify(addEvent)}\n\n`);
292
+ log('debug', 'Sent output_item.add event');
293
+
267
294
  for await (const chunk of stream) {
268
295
  buffer += decoder.decode(chunk, { stream: true });
269
296
  const lines = buffer.split('\n');
@@ -286,24 +313,32 @@ async function streamChatToResponses(stream, res) {
286
313
  if (data === '[DONE]') {
287
314
  log('info', `Stream end received - wrote ${deltaCount} deltas total`);
288
315
 
289
- // Send response.completed event (required by Codex Responses API)
290
- // Map Z.AI usage format to Responses API format
316
+ // Send response.completed event in OpenAI Responses API format
291
317
  const zaiUsage = lastParsed?.usage;
292
318
  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
- }
319
+ type: 'response.completed',
320
+ response: {
321
+ id: responseId,
322
+ status: 'completed',
323
+ output: [{
324
+ type: 'message',
325
+ role: 'assistant',
326
+ content: [{ type: 'output_text', text: '' }]
327
+ }],
328
+ usage: zaiUsage ? {
329
+ input_tokens: zaiUsage.prompt_tokens || 0,
330
+ output_tokens: zaiUsage.completion_tokens || 0,
331
+ total_tokens: zaiUsage.total_tokens || 0
332
+ } : {
333
+ input_tokens: 0,
334
+ output_tokens: 0,
335
+ total_tokens: 0
336
+ }
337
+ },
338
+ sequence_number: deltaCount
303
339
  };
304
340
 
305
- log('info', 'Sending response.completed event:', JSON.stringify(completedEvent));
306
- res.write(`event: response.completed\n`);
341
+ log('info', 'Sending response.completed event');
307
342
  res.write(`data: ${JSON.stringify(completedEvent)}\n\n`);
308
343
  log('info', 'Sent response.completed event');
309
344
  return;
@@ -311,26 +346,31 @@ async function streamChatToResponses(stream, res) {
311
346
 
312
347
  try {
313
348
  const parsed = JSON.parse(data);
314
- lastParsed = parsed; // Save for later use in completed event
349
+ lastParsed = parsed;
315
350
  log('debug', 'Parsed SSE:', JSON.stringify(parsed).substring(0, 150));
316
351
 
317
352
  const delta = parsed.choices?.[0]?.delta;
318
-
319
- // Z.AI uses reasoning_content instead of content
320
353
  const content = delta?.content || delta?.reasoning_content || '';
321
354
 
322
355
  if (content) {
323
356
  deltaCount++;
324
357
  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`);
358
+ // OpenAI Responses API format for text delta
359
+ const deltaEvent = {
360
+ type: 'response.output_text.delta',
361
+ delta: content,
362
+ output_index: 0,
363
+ item_id: itemId,
364
+ sequence_number: deltaCount - 1
365
+ };
366
+ res.write(`data: ${JSON.stringify(deltaEvent)}\n\n`);
327
367
  }
328
368
  } catch (e) {
329
369
  log('warn', 'Failed to parse SSE chunk:', e.message, 'data:', data.substring(0, 100));
330
370
  }
331
371
  }
332
372
 
333
- if (chunkCount > 100) {
373
+ if (chunkCount > 1000) {
334
374
  log('warn', 'Too many chunks, possible loop');
335
375
  return;
336
376
  }
@@ -413,6 +453,8 @@ async function handlePostRequest(req, res) {
413
453
 
414
454
  // Handle streaming response
415
455
  if (upstreamBody.stream) {
456
+ const responseId = 'resp_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
457
+ const itemId = 'item_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
416
458
  log('info', 'Starting streaming response');
417
459
  res.writeHead(200, {
418
460
  'Content-Type': 'text/event-stream; charset=utf-8',
@@ -421,7 +463,7 @@ async function handlePostRequest(req, res) {
421
463
  });
422
464
 
423
465
  try {
424
- await streamChatToResponses(upstreamResponse.body, res);
466
+ await streamChatToResponses(upstreamResponse.body, res, responseId, itemId);
425
467
  log('info', 'Streaming completed');
426
468
  } catch (e) {
427
469
  log('error', 'Streaming error:', e);