codemini-cli 0.5.12 → 0.6.1

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 (32) hide show
  1. package/codemini-web/dist/assets/{highlighted-body-OFNGDK62-B-G99D0A.js → highlighted-body-OFNGDK62-DaefZcrn.js} +1 -1
  2. package/codemini-web/dist/assets/{index-DIGUEzan.js → index-Bk_gka_s.js} +98 -93
  3. package/codemini-web/dist/assets/index-BzSHdGYY.css +2 -0
  4. package/codemini-web/dist/assets/mermaid-GHXKKRXX-BSt5tun0.js +1 -0
  5. package/codemini-web/dist/index.html +2 -2
  6. package/codemini-web/dist/logos/chatglm-color.svg +1 -0
  7. package/codemini-web/dist/logos/claude-color.svg +1 -0
  8. package/codemini-web/dist/logos/codemini-banner.png +0 -0
  9. package/codemini-web/dist/logos/codemini_banner.png +0 -0
  10. package/codemini-web/dist/logos/deepseek-color.svg +1 -0
  11. package/codemini-web/dist/logos/gemini-color.svg +1 -0
  12. package/codemini-web/dist/logos/glm-color.svg +1 -0
  13. package/codemini-web/dist/logos/google-color.svg +1 -0
  14. package/codemini-web/dist/logos/kimi-color.svg +1 -0
  15. package/codemini-web/dist/logos/minimax-color.svg +1 -0
  16. package/codemini-web/dist/logos/moonshot.svg +1 -0
  17. package/codemini-web/dist/logos/nvidia-color.svg +1 -0
  18. package/codemini-web/dist/logos/openai.svg +1 -0
  19. package/codemini-web/dist/logos/qwen-color.svg +1 -0
  20. package/codemini-web/dist/logos/zhipu-color.svg +1 -0
  21. package/codemini-web/lib/runtime-bridge.js +203 -63
  22. package/package.json +1 -1
  23. package/src/core/agent-loop.js +25 -22
  24. package/src/core/chat-runtime.js +382 -52
  25. package/src/core/config-store.js +24 -31
  26. package/src/core/provider/anthropic.js +12 -9
  27. package/src/core/provider/openai-compatible.js +80 -50
  28. package/src/core/session-store.js +63 -24
  29. package/src/core/tools.js +15 -18
  30. package/codemini-web/dist/assets/index-Dkq1DdDX.css +0 -2
  31. package/codemini-web/dist/assets/mermaid-GHXKKRXX-va2Kl89u.js +0 -1
  32. /package/codemini-web/dist/{codemini_logo.png → logos/codemini_logo.png} +0 -0
@@ -47,21 +47,123 @@ function updateToolInSegments(segments, toolId, updater) {
47
47
  });
48
48
  }
49
49
 
50
- function appendTextSegment(segments, delta, isStreaming = true) {
51
- const value = String(delta || '');
52
- if (!value) return segments || [];
53
- const current = Array.isArray(segments) ? segments : [];
50
+ function appendTextSegment(segments, delta, isStreaming = true) {
51
+ const value = String(delta || '');
52
+ if (!value) return segments || [];
53
+ const current = Array.isArray(segments) ? segments : [];
54
54
  const last = current[current.length - 1];
55
55
  if (last?.type === 'text') {
56
56
  return [
57
57
  ...current.slice(0, -1),
58
58
  { ...last, text: `${last.text || ''}${value}`, isStreaming }
59
59
  ];
60
- }
61
- return [...current, { type: 'text', text: value, isStreaming }];
62
- }
63
-
64
- function createPlanStepUiMessage(event) {
60
+ }
61
+ return [...current, { type: 'text', text: value, isStreaming }];
62
+ }
63
+
64
+ function replaceTextSegment(segments, text, isStreaming = false) {
65
+ const value = String(text || '');
66
+ const current = Array.isArray(segments) ? segments : [];
67
+ const index = current.findLastIndex((seg) => seg?.type === 'text');
68
+ if (index === -1) return value ? [...current, { type: 'text', text: value, isStreaming }] : current;
69
+ return current.map((seg, i) => (
70
+ i === index ? { ...seg, text: value, isStreaming } : seg
71
+ ));
72
+ }
73
+
74
+ function appendThinkingSegment(segments, delta, isStreaming = true) {
75
+ const value = String(delta || '');
76
+ if (!value) return segments || [];
77
+ const current = Array.isArray(segments) ? segments : [];
78
+ const now = new Date().toISOString();
79
+ const nowMs = Date.parse(now);
80
+ const last = current[current.length - 1];
81
+ if (last?.type === 'thinking') {
82
+ const startedAt = last.startedAt || now;
83
+ return [
84
+ ...current.slice(0, -1),
85
+ {
86
+ ...last,
87
+ text: `${last.text || ''}${value}`,
88
+ isStreaming,
89
+ startedAt,
90
+ endedAt: isStreaming ? null : (last.endedAt || now),
91
+ durationMs: Math.max(Number(last.durationMs || 0), nowMs - Date.parse(startedAt))
92
+ }
93
+ ];
94
+ }
95
+ return [...current, { type: 'thinking', text: value, isStreaming, startedAt: now, endedAt: isStreaming ? null : now, durationMs: isStreaming ? 0 : null }];
96
+ }
97
+
98
+ function resolveThinkingDurationMs(seg, endedAt) {
99
+ const explicit = Number(seg?.durationMs);
100
+ const startMs = Date.parse(seg?.startedAt || '');
101
+ const endMs = Date.parse(seg?.endedAt || endedAt || '');
102
+ const measured = Number.isFinite(startMs) && Number.isFinite(endMs) ? Math.max(0, endMs - startMs) : null;
103
+ if (Number.isFinite(explicit) && measured != null) return Math.max(explicit, measured);
104
+ if (Number.isFinite(explicit)) return Math.max(0, explicit);
105
+ return measured;
106
+ }
107
+
108
+ function finishThinkingSegments(segments) {
109
+ const endedAt = new Date().toISOString();
110
+ return (Array.isArray(segments) ? segments : []).map((seg) => (
111
+ seg.type === 'thinking'
112
+ ? {
113
+ ...seg,
114
+ isStreaming: false,
115
+ endedAt: seg.endedAt || endedAt,
116
+ durationMs: resolveThinkingDurationMs(seg, endedAt)
117
+ }
118
+ : seg
119
+ ));
120
+ }
121
+
122
+ function getReasoningTextFromAssistantMessage(message = {}) {
123
+ if (typeof message.reasoning_content === 'string' && message.reasoning_content.trim()) {
124
+ return message.reasoning_content.trim();
125
+ }
126
+ if (!Array.isArray(message.reasoning_details)) return '';
127
+ return message.reasoning_details
128
+ .map((block) => {
129
+ if (!block || typeof block !== 'object') return '';
130
+ if (block.type === 'thinking') return block.thinking || block.text || '';
131
+ if (block.type === 'reasoning' || block.type === 'reasoning_content') return block.text || block.reasoning_content || '';
132
+ if (block.type === 'redacted_thinking') return '[redacted thinking]';
133
+ return '';
134
+ })
135
+ .filter(Boolean)
136
+ .join('\n\n')
137
+ .trim();
138
+ }
139
+
140
+ function hasThinkingSegment(segments) {
141
+ return (Array.isArray(segments) ? segments : []).some((seg) => seg.type === 'thinking' && String(seg.text || '').trim());
142
+ }
143
+
144
+ function normalizeUiUsage(usage) {
145
+ if (!usage || typeof usage !== 'object') return null;
146
+ const out = {};
147
+ for (const key of ['inputTokens', 'outputTokens', 'totalTokens', 'cachedInputTokens', 'cacheMissInputTokens', 'cacheWriteInputTokens', 'reasoningOutputTokens', 'requests']) {
148
+ const value = Number(usage?.[key]);
149
+ if (Number.isFinite(value)) out[key] = Math.max(0, Math.round(value));
150
+ }
151
+ return Object.keys(out).length ? out : null;
152
+ }
153
+
154
+ function mergeUiUsage(left, right) {
155
+ const a = normalizeUiUsage(left);
156
+ const b = normalizeUiUsage(right);
157
+ if (!a) return b;
158
+ if (!b) return a;
159
+ const out = {};
160
+ for (const key of ['inputTokens', 'outputTokens', 'totalTokens', 'cachedInputTokens', 'cacheMissInputTokens', 'cacheWriteInputTokens', 'reasoningOutputTokens', 'requests']) {
161
+ out[key] = Math.max(0, Math.round(Number(a[key] || 0) + Number(b[key] || 0)));
162
+ }
163
+ return out;
164
+ }
165
+
166
+ function createPlanStepUiMessage(event) {
65
167
  return {
66
168
  id: `plan-step-${event.step}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
67
169
  role: event.role || 'general',
@@ -204,50 +306,81 @@ export class RuntimeBridge {
204
306
  }
205
307
  break;
206
308
  }
207
- case 'assistant:delta': {
208
- const delta = stripPlanProgressText(event.text);
209
- if (this.#uiActiveMsgId && delta) {
210
- this.#updateUiMessage(this.#uiActiveMsgId, (message) => ({
211
- ...message,
212
- segments: appendTextSegment(message.segments, delta, true)
213
- }));
214
- }
215
- break;
216
- }
217
- case 'assistant:response': {
218
- if (this.#uiActiveMsgId && event.text) {
219
- const text = stripPlanProgressText(event.text);
220
- this.#updateUiMessage(this.#uiActiveMsgId, (message) => ({
221
- ...message,
222
- segments: text ? [{ type: 'text', text, isStreaming: false }] : message.segments
223
- }));
224
- }
225
- break;
226
- }
309
+ case 'assistant:delta': {
310
+ const delta = stripPlanProgressText(event.text);
311
+ if (this.#uiActiveMsgId && delta) {
312
+ this.#updateUiMessage(this.#uiActiveMsgId, (message) => ({
313
+ ...message,
314
+ segments: appendTextSegment(finishThinkingSegments(message.segments), delta, true)
315
+ }));
316
+ }
317
+ break;
318
+ }
319
+ case 'assistant:reasoning_delta': {
320
+ if (this.#uiActiveMsgId && event.text) {
321
+ this.#updateUiMessage(this.#uiActiveMsgId, (message) => ({
322
+ ...message,
323
+ segments: appendThinkingSegment(message.segments, event.text, true)
324
+ }));
325
+ }
326
+ break;
327
+ }
328
+ case 'assistant:response': {
329
+ const reasoningText = getReasoningTextFromAssistantMessage(event.assistantMessage);
330
+ if (this.#uiActiveMsgId && reasoningText) {
331
+ this.#updateUiMessage(this.#uiActiveMsgId, (message) => ({
332
+ ...message,
333
+ segments: hasThinkingSegment(message.segments)
334
+ ? message.segments
335
+ : appendThinkingSegment(message.segments, reasoningText, false)
336
+ }));
337
+ }
338
+ if (this.#uiActiveMsgId && event.text) {
339
+ const text = stripPlanProgressText(event.text);
340
+ this.#updateUiMessage(this.#uiActiveMsgId, (message) => ({
341
+ ...message,
342
+ segments: text
343
+ ? replaceTextSegment(finishThinkingSegments(message.segments), text, false)
344
+ : message.segments
345
+ }));
346
+ }
347
+ if (this.#uiActiveMsgId) {
348
+ this.#updateUiMessage(this.#uiActiveMsgId, (message) => ({
349
+ ...message,
350
+ segments: finishThinkingSegments(message.segments),
351
+ usage: mergeUiUsage(message.usage, event.usage || event.assistantMessage?.usage) || message.usage || null
352
+ }));
353
+ }
354
+ break;
355
+ }
227
356
  case 'tool:start': {
228
- if (this.#uiActiveMsgId) {
229
- const toolCard = { id: event.id, name: event.name, arguments: event.arguments, status: 'running', durationMs: null, summary: '', result: '' };
230
- this.#updateUiMessage(this.#uiActiveMsgId, (message) => ({
231
- ...message,
232
- segments: addToolToSegments(message.segments, toolCard)
233
- }));
234
- }
235
- break;
236
- }
237
- case 'tool:end': {
238
- if (this.#uiActiveMsgId) {
239
- this.#updateUiMessage(this.#uiActiveMsgId, (message) => ({
240
- ...message,
241
- segments: updateToolInSegments(message.segments, event.id, (card) => ({
242
- ...card,
243
- status: 'done',
244
- durationMs: event.durationMs,
245
- summary: event.summary || card.summary
246
- }))
247
- }));
248
- }
357
+ if (this.#uiActiveMsgId) {
358
+ const toolCard = { id: event.id, name: event.name, arguments: event.arguments, status: 'running', durationMs: null, summary: '', result: '' };
359
+ this.#updateUiMessage(this.#uiActiveMsgId, (message) => ({
360
+ ...message,
361
+ segments: addToolToSegments(finishThinkingSegments(message.segments), toolCard)
362
+ }));
363
+ }
249
364
  break;
250
365
  }
366
+ case 'tool:end': {
367
+ if (this.#uiActiveMsgId) {
368
+ this.#updateUiMessage(this.#uiActiveMsgId, (message) => ({
369
+ ...message,
370
+ fileChanges: event.fileChange?.path
371
+ ? [...(Array.isArray(message.fileChanges) ? message.fileChanges : []), event.fileChange]
372
+ : (Array.isArray(message.fileChanges) ? message.fileChanges : []),
373
+ segments: updateToolInSegments(message.segments, event.id, (card) => ({
374
+ ...card,
375
+ status: 'done',
376
+ durationMs: event.durationMs,
377
+ summary: event.summary || card.summary,
378
+ ...(event.fileChange ? { fileChange: event.fileChange } : {})
379
+ }))
380
+ }));
381
+ }
382
+ break;
383
+ }
251
384
  case 'tool:result': {
252
385
  if (this.#uiActiveMsgId) {
253
386
  this.#updateUiMessage(this.#uiActiveMsgId, (message) => ({
@@ -379,12 +512,13 @@ export class RuntimeBridge {
379
512
  this.#recordUiEvent(event);
380
513
  this.#broadcast(event);
381
514
  }, options).then((result) => {
382
- if (this.#uiActiveMsgId) {
383
- this.#updateUiMessage(this.#uiActiveMsgId, (message) => ({
384
- ...message,
385
- segments: message.segments.map((seg) => seg.type === 'text' ? { ...seg, isStreaming: false } : seg)
386
- }));
387
- }
515
+ if (this.#uiActiveMsgId) {
516
+ this.#updateUiMessage(this.#uiActiveMsgId, (message) => ({
517
+ ...message,
518
+ segments: finishThinkingSegments(message.segments)
519
+ .map((seg) => seg.type === 'text' ? { ...seg, isStreaming: false } : seg)
520
+ }));
521
+ }
388
522
  this.#uiActiveMsgId = null;
389
523
  this.#uiPlanStepIds = new Map();
390
524
  this.#broadcast({ type: 'submit:done', result: { type: result.type, aborted: result.aborted, text: result.text } });
@@ -474,17 +608,23 @@ export class RuntimeBridge {
474
608
  return messages
475
609
  .filter(m => m.role !== 'system')
476
610
  .map(m => ({
477
- role: m.role,
478
- content: typeof m.content === 'string' ? m.content : (Array.isArray(m.content) ? m.content.map(c => c.text || '').join('') : ''),
479
- toolCalls: m.tool_calls || [],
611
+ role: m.role,
612
+ content: typeof m.content === 'string' ? m.content : (Array.isArray(m.content) ? m.content.map(c => c.text || '').join('') : ''),
613
+ reasoningContent: typeof m.reasoning_content === 'string' ? m.reasoning_content : '',
614
+ reasoningDetails: Array.isArray(m.reasoning_details) ? m.reasoning_details : [],
615
+ reasoningStartedAt: m.reasoning_started_at || null,
616
+ reasoningEndedAt: m.reasoning_ended_at || null,
617
+ reasoningDurationMs: Number.isFinite(Number(m.reasoning_duration_ms)) ? Number(m.reasoning_duration_ms) : null,
618
+ toolCalls: m.tool_calls || [],
480
619
  toolCallId: m.tool_call_id || null,
481
620
  toolSummary: m.role === 'tool' ? summarizeHistoricalToolMessage(m) : null,
482
621
  toolDurationMs: Number.isFinite(Number(m.tool_duration_ms)) ? Number(m.tool_duration_ms) : null,
483
622
  toolStatus: m.tool_status || null,
484
- planTranscript: Array.isArray(m.plan_transcript) ? m.plan_transcript : null,
485
- at: m.at || null
486
- }));
487
- }
623
+ planTranscript: Array.isArray(m.plan_transcript) ? m.plan_transcript : null,
624
+ usage: normalizeUiUsage(m.usage),
625
+ at: m.at || null
626
+ }));
627
+ }
488
628
 
489
629
  getSessionCompactMeta() {
490
630
  const compact = this.#runtime.getSessionCompact();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codemini-cli",
3
- "version": "0.5.12",
3
+ "version": "0.6.1",
4
4
  "description": "An extremely restrained coding + tasks CLI. Every platform. Every terminal. Minimal by design.",
5
5
  "keywords": [
6
6
  "cli",
@@ -252,13 +252,15 @@ function extractFileChange(toolName, result) {
252
252
  const isCreate = action === 'create';
253
253
  const added = Number(result.lines_added || 0);
254
254
  const removed = Number(result.lines_removed || 0);
255
- return {
256
- path: String(result.path || ''),
257
- action: isCreate ? 'create' : 'edit',
258
- linesAdded: added,
259
- linesRemoved: removed
260
- };
261
- }
255
+ return {
256
+ path: String(result.path || ''),
257
+ action: isCreate ? 'create' : 'edit',
258
+ linesAdded: added,
259
+ linesRemoved: removed,
260
+ changedLine: Number(result.changed_line || 0),
261
+ diffPreview: String(result.diff_preview || '')
262
+ };
263
+ }
262
264
 
263
265
  return null;
264
266
  }
@@ -597,13 +599,13 @@ export async function runAgentLoop({
597
599
  const assistantText = completion.text || '';
598
600
  lastAssistantText = assistantText || lastAssistantText;
599
601
 
600
- const assistantMessage = completion?.assistantMessage
601
- ? {
602
- ...completion.assistantMessage,
603
- role: 'assistant',
604
- content: completion.assistantMessage.content ?? completion?.content ?? assistantText
605
- }
606
- : { role: 'assistant', content: completion?.content ?? assistantText };
602
+ const assistantMessage = completion?.assistantMessage
603
+ ? {
604
+ ...completion.assistantMessage,
605
+ role: 'assistant',
606
+ content: completion.assistantMessage.content ?? completion?.content ?? assistantText
607
+ }
608
+ : { role: 'assistant', content: completion?.content ?? assistantText };
607
609
  if (!Array.isArray(assistantMessage.tool_calls) && toolCalls.length > 0) {
608
610
  assistantMessage.tool_calls = toolCalls.map((tc) => ({
609
611
  id: tc.id,
@@ -616,10 +618,11 @@ export async function runAgentLoop({
616
618
  onEvent({
617
619
  type: 'assistant:response',
618
620
  step: step + 1,
619
- text: assistantText,
620
- toolCalls: toolCalls.map((tc) => tc.name),
621
- assistantMessage
622
- });
621
+ text: assistantText,
622
+ toolCalls: toolCalls.map((tc) => tc.name),
623
+ usage: completion.usage || null,
624
+ assistantMessage
625
+ });
623
626
  }
624
627
 
625
628
  if (toolCalls.length === 0) {
@@ -710,10 +713,10 @@ export async function runAgentLoop({
710
713
  });
711
714
  approvalArgs = { ...args, _risk: evaluation.risk, _evaluation: evaluation };
712
715
  /* LLM says low-risk + allow → auto-approve, skip confirmation panel */
713
- if (evaluation.risk === 'low' && evaluation.recommendation === 'allow') {
714
- approvalResults.set(call.id, { approved: true, args: approvalArgs });
715
- continue;
716
- }
716
+ if (executionMode !== 'normal' && evaluation.risk === 'low' && evaluation.recommendation === 'allow') {
717
+ approvalResults.set(call.id, { approved: true, args: approvalArgs });
718
+ continue;
719
+ }
717
720
  } catch (_) {
718
721
  approvalArgs = { ...args, _risk: 'high', _evaluation: null };
719
722
  }