@mmmbuto/zai-codex-bridge 0.1.7 → 0.1.9

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 +54 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmmbuto/zai-codex-bridge",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
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
@@ -80,10 +80,24 @@ function translateResponsesToChat(request) {
80
80
  content: request.input
81
81
  });
82
82
  } else if (Array.isArray(request.input)) {
83
- // Array of message objects
83
+ // Array of ResponseItem objects - filter only Message items with role
84
84
  for (const item of request.input) {
85
+ // Only process items with a 'role' field (Message items)
86
+ // Skip Reasoning, FunctionCall, LocalShellCall, etc.
87
+ if (!item.role) continue;
88
+
89
+ // Map non-standard roles to Z.AI-compatible roles
90
+ // Z.AI accepts: system, user, assistant
91
+ let role = item.role;
92
+ if (role === 'developer') {
93
+ role = 'user'; // Map developer to user
94
+ } else if (role !== 'system' && role !== 'user' && role !== 'assistant') {
95
+ // Skip any other non-standard roles
96
+ continue;
97
+ }
98
+
85
99
  const msg = {
86
- role: item.role,
100
+ role: role,
87
101
  content: flattenContent(item.content)
88
102
  };
89
103
 
@@ -127,7 +141,21 @@ function translateResponsesToChat(request) {
127
141
  }
128
142
 
129
143
  if (request.tools && Array.isArray(request.tools)) {
130
- chatRequest.tools = request.tools;
144
+ // Filter out tools with null or empty function
145
+ chatRequest.tools = request.tools.filter(tool => {
146
+ if (tool.type === 'function') {
147
+ // Check if function has required fields
148
+ return tool.function && typeof tool.function === 'object' &&
149
+ tool.function.name && tool.function.name.length > 0 &&
150
+ tool.function.parameters !== undefined && tool.function.parameters !== null;
151
+ }
152
+ // Keep non-function tools (if any)
153
+ return true;
154
+ });
155
+ // Only add tools array if there are valid tools
156
+ if (chatRequest.tools.length === 0) {
157
+ delete chatRequest.tools;
158
+ }
131
159
  }
132
160
 
133
161
  if (request.tool_choice) {
@@ -210,7 +238,9 @@ async function makeUpstreamRequest(path, body, headers) {
210
238
  base: ZAI_BASE_URL,
211
239
  hasAuth: !!upstreamHeaders.Authorization,
212
240
  bodyKeys: Object.keys(body),
213
- bodyPreview: JSON.stringify(body).substring(0, 200)
241
+ bodyPreview: JSON.stringify(body).substring(0, 800),
242
+ messagesCount: body.messages?.length || 0,
243
+ allRoles: body.messages?.map(m => m.role) || []
214
244
  });
215
245
 
216
246
  const response = await fetch(url, {
@@ -229,6 +259,8 @@ async function streamChatToResponses(stream, res) {
229
259
  const decoder = new TextDecoder();
230
260
  let buffer = '';
231
261
  let chunkCount = 0;
262
+ let deltaCount = 0;
263
+ let lastParsed = null; // Keep track of last parsed SSE for ID extraction
232
264
 
233
265
  log('debug', 'Starting to process stream');
234
266
 
@@ -252,14 +284,27 @@ async function streamChatToResponses(stream, res) {
252
284
 
253
285
  // Check for stream end
254
286
  if (data === '[DONE]') {
255
- log('debug', 'Stream end received');
256
- res.write(`event: completed\n`);
257
- res.write(`data: ${JSON.stringify({ status: 'completed' })}\n\n`);
287
+ log('info', `Stream end received - wrote ${deltaCount} deltas total`);
288
+
289
+ // Send response.completed event (required by Codex Responses API)
290
+ const completedEvent = {
291
+ id: lastParsed?.id || 'msg_' + Date.now(),
292
+ usage: lastParsed?.usage || {
293
+ input_tokens: 0,
294
+ output_tokens: 0,
295
+ total_tokens: 0
296
+ }
297
+ };
298
+
299
+ res.write(`event: response.completed\n`);
300
+ res.write(`data: ${JSON.stringify(completedEvent)}\n\n`);
301
+ log('info', 'Sent response.completed event');
258
302
  return;
259
303
  }
260
304
 
261
305
  try {
262
306
  const parsed = JSON.parse(data);
307
+ lastParsed = parsed; // Save for later use in completed event
263
308
  log('debug', 'Parsed SSE:', JSON.stringify(parsed).substring(0, 150));
264
309
 
265
310
  const delta = parsed.choices?.[0]?.delta;
@@ -268,6 +313,7 @@ async function streamChatToResponses(stream, res) {
268
313
  const content = delta?.content || delta?.reasoning_content || '';
269
314
 
270
315
  if (content) {
316
+ deltaCount++;
271
317
  log('debug', 'Writing delta:', content.substring(0, 30));
272
318
  res.write(`event: output.text.delta\n`);
273
319
  res.write(`data: ${JSON.stringify({ value: content })}\n\n`);
@@ -283,7 +329,7 @@ async function streamChatToResponses(stream, res) {
283
329
  }
284
330
  }
285
331
 
286
- log('debug', 'Stream ended naturally');
332
+ log('info', `Stream ended naturally - wrote ${deltaCount} deltas`);
287
333
  }
288
334
 
289
335
  /**