coding-tool-x 3.3.2 → 3.3.4

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.
@@ -279,6 +279,7 @@ module.exports = (config) => {
279
279
 
280
280
  const stream = fs.createReadStream(sessionFile, { encoding: 'utf8' });
281
281
  const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
282
+ let lastAssistantModel = null;
282
283
 
283
284
  try {
284
285
  for await (const line of rl) {
@@ -297,31 +298,45 @@ module.exports = (config) => {
297
298
  }
298
299
 
299
300
  if (json.type === 'user' || json.type === 'assistant') {
301
+ const resolvedModel = json.message?.model || json.model || lastAssistantModel || null;
302
+ const messageId = json.message?.id || json.uuid || null;
300
303
  const message = {
301
304
  type: json.type,
302
305
  content: null,
303
306
  timestamp: json.timestamp || null,
304
- model: json.model || null
307
+ model: resolvedModel,
308
+ messageId,
309
+ subtype: null
305
310
  };
311
+ let deferredToolResultContent = '';
306
312
 
307
313
  if (json.type === 'user') {
308
314
  if (typeof json.message?.content === 'string') {
309
315
  message.content = json.message.content;
310
316
  } else if (Array.isArray(json.message?.content)) {
311
- const parts = [];
317
+ const userParts = [];
318
+ const toolResultParts = [];
312
319
  for (const item of json.message.content) {
313
320
  if (item.type === 'text' && item.text) {
314
- parts.push(item.text);
321
+ userParts.push(item.text);
315
322
  } else if (item.type === 'tool_result') {
316
323
  const resultContent = typeof item.content === 'string'
317
324
  ? item.content
318
325
  : JSON.stringify(item.content, null, 2);
319
- parts.push(`**[工具结果]**\n\`\`\`\n${resultContent}\n\`\`\``);
326
+ toolResultParts.push(`**[工具结果]**\n\`\`\`\n${resultContent}\n\`\`\``);
320
327
  } else if (item.type === 'image') {
321
- parts.push('[图片]');
328
+ userParts.push('[图片]');
322
329
  }
323
330
  }
324
- message.content = parts.join('\n\n') || '[工具交互]';
331
+
332
+ if (userParts.length > 0) {
333
+ message.content = userParts.join('\n\n');
334
+ }
335
+
336
+ // Claude tool_result is carried in a "user" envelope, but should be rendered as AI tool output.
337
+ if (toolResultParts.length > 0) {
338
+ deferredToolResultContent = toolResultParts.join('\n\n');
339
+ }
325
340
  }
326
341
  } else if (json.type === 'assistant') {
327
342
  if (Array.isArray(json.message?.content)) {
@@ -340,11 +355,26 @@ module.exports = (config) => {
340
355
  } else if (typeof json.message?.content === 'string') {
341
356
  message.content = json.message.content;
342
357
  }
358
+
359
+ if (message.model) {
360
+ lastAssistantModel = message.model;
361
+ }
343
362
  }
344
363
 
345
364
  if (message.content && message.content !== 'Warmup') {
346
365
  allMessages.push(message);
347
366
  }
367
+
368
+ if (deferredToolResultContent) {
369
+ allMessages.push({
370
+ type: 'assistant',
371
+ subtype: 'tool_result',
372
+ content: deferredToolResultContent,
373
+ timestamp: json.timestamp || null,
374
+ model: resolvedModel,
375
+ messageId: messageId ? `${messageId}-tool-result` : null
376
+ });
377
+ }
348
378
  }
349
379
  } catch (err) {
350
380
  // Skip invalid lines
@@ -2125,6 +2125,16 @@ function sendChatCompletionsSse(res, responseObject) {
2125
2125
  const message = responseObject?.choices?.[0]?.message || {};
2126
2126
  const text = message?.content || '';
2127
2127
  const toolCalls = Array.isArray(message?.tool_calls) ? message.tool_calls : [];
2128
+ const streamedToolCalls = toolCalls.map((toolCall, index) => {
2129
+ const numericIndex = Number(toolCall?.index);
2130
+ const normalizedIndex = Number.isFinite(numericIndex) ? numericIndex : index;
2131
+
2132
+ if (toolCall && typeof toolCall === 'object' && !Array.isArray(toolCall)) {
2133
+ return { ...toolCall, index: normalizedIndex };
2134
+ }
2135
+
2136
+ return { index: normalizedIndex };
2137
+ });
2128
2138
  const finishReason = responseObject?.choices?.[0]?.finish_reason || 'stop';
2129
2139
 
2130
2140
  setSseHeaders(res);
@@ -2140,7 +2150,7 @@ function sendChatCompletionsSse(res, responseObject) {
2140
2150
  delta: {
2141
2151
  role: 'assistant',
2142
2152
  ...(text ? { content: text } : {}),
2143
- ...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {})
2153
+ ...(streamedToolCalls.length > 0 ? { tool_calls: streamedToolCalls } : {})
2144
2154
  },
2145
2155
  finish_reason: null
2146
2156
  }
@@ -63,11 +63,22 @@ function extractSessionMeta(lines) {
63
63
  */
64
64
  function extractMessages(lines) {
65
65
  const messages = [];
66
+ let currentTurnModel = null;
67
+ let lastAssistantModel = null;
66
68
 
67
69
  lines.forEach(line => {
70
+ if (line.type === 'turn_context') {
71
+ const model = line.payload?.model;
72
+ if (typeof model === 'string' && model.trim()) {
73
+ currentTurnModel = model.trim();
74
+ }
75
+ return;
76
+ }
77
+
68
78
  if (line.type !== 'response_item') return;
69
79
 
70
80
  const payload = line.payload;
81
+ const resolvedModel = payload?.model || currentTurnModel || lastAssistantModel || null;
71
82
 
72
83
  // 用户/助手消息
73
84
  if (payload.type === 'message') {
@@ -81,8 +92,12 @@ function extractMessages(lines) {
81
92
  messages.push({
82
93
  role: payload.role,
83
94
  content: text,
84
- timestamp: line.timestamp
95
+ timestamp: line.timestamp,
96
+ model: payload.role === 'assistant' ? resolvedModel : null
85
97
  });
98
+ if (payload.role === 'assistant' && resolvedModel) {
99
+ lastAssistantModel = resolvedModel;
100
+ }
86
101
  }
87
102
  }
88
103
 
@@ -101,8 +116,12 @@ function extractMessages(lines) {
101
116
  name: payload.name,
102
117
  arguments: parsedArguments,
103
118
  callId: payload.call_id,
104
- timestamp: line.timestamp
119
+ timestamp: line.timestamp,
120
+ model: resolvedModel
105
121
  });
122
+ if (resolvedModel) {
123
+ lastAssistantModel = resolvedModel;
124
+ }
106
125
  }
107
126
 
108
127
  // 工具输出
@@ -119,8 +138,12 @@ function extractMessages(lines) {
119
138
  role: 'tool_output',
120
139
  callId: payload.call_id,
121
140
  output: parsedOutput,
122
- timestamp: line.timestamp
141
+ timestamp: line.timestamp,
142
+ model: resolvedModel
123
143
  });
144
+ if (resolvedModel) {
145
+ lastAssistantModel = resolvedModel;
146
+ }
124
147
  }
125
148
 
126
149
  // 推理内容
@@ -135,8 +158,12 @@ function extractMessages(lines) {
135
158
  messages.push({
136
159
  role: 'reasoning',
137
160
  content: text,
138
- timestamp: line.timestamp
161
+ timestamp: line.timestamp,
162
+ model: resolvedModel
139
163
  });
164
+ if (resolvedModel) {
165
+ lastAssistantModel = resolvedModel;
166
+ }
140
167
  }
141
168
  }
142
169
  });
@@ -359,7 +359,19 @@ function getSessionRowsByProjectId(projectId) {
359
359
  s.time_created,
360
360
  s.time_updated,
361
361
  s.time_compacting,
362
- s.time_archived
362
+ s.time_archived,
363
+ (
364
+ COALESCE((
365
+ SELECT SUM(length(CAST(COALESCE(m.data, '') AS BLOB)))
366
+ FROM message m
367
+ WHERE m.session_id = s.id
368
+ ), 0) +
369
+ COALESCE((
370
+ SELECT SUM(length(CAST(COALESCE(p.data, '') AS BLOB)))
371
+ FROM part p
372
+ WHERE p.session_id = s.id
373
+ ), 0)
374
+ ) AS size
363
375
  FROM session s
364
376
  WHERE s.project_id = ${sqlQuote(projectId)}
365
377
  AND s.time_archived IS NULL
@@ -387,7 +399,19 @@ function getSessionRowById(sessionId) {
387
399
  s.time_created,
388
400
  s.time_updated,
389
401
  s.time_compacting,
390
- s.time_archived
402
+ s.time_archived,
403
+ (
404
+ COALESCE((
405
+ SELECT SUM(length(CAST(COALESCE(m.data, '') AS BLOB)))
406
+ FROM message m
407
+ WHERE m.session_id = s.id
408
+ ), 0) +
409
+ COALESCE((
410
+ SELECT SUM(length(CAST(COALESCE(p.data, '') AS BLOB)))
411
+ FROM part p
412
+ WHERE p.session_id = s.id
413
+ ), 0)
414
+ ) AS size
391
415
  FROM session s
392
416
  WHERE s.id = ${sqlQuote(sessionId)}
393
417
  LIMIT 1
@@ -426,11 +450,12 @@ function getPartRowsBySessionId(sessionId) {
426
450
  }
427
451
 
428
452
  function normalizeSession(session, projectId = null) {
453
+ const size = Number(session?.size);
429
454
  return {
430
455
  sessionId: session.id,
431
456
  projectName: projectId || session.project_id,
432
457
  mtime: toIsoTime(session.time_updated) || new Date().toISOString(),
433
- size: 0,
458
+ size: Number.isFinite(size) && size > 0 ? size : 0,
434
459
  filePath: '',
435
460
  gitBranch: null,
436
461
  firstMessage: session.title || session.slug || null,