codemini-cli 0.5.7 → 0.5.8

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.
@@ -0,0 +1 @@
1
+ import{i as e}from"./index-Cy4HN-FS.js";export{e as Mermaid};
@@ -14,9 +14,9 @@
14
14
  document.documentElement.dataset.palette = palette;
15
15
  })();
16
16
  </script>
17
- <script type="module" crossorigin src="/assets/index-Bvd2jj3t.js"></script>
17
+ <script type="module" crossorigin src="/assets/index-Cy4HN-FS.js"></script>
18
18
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-S-ySWqyJ.js">
19
- <link rel="stylesheet" crossorigin href="/assets/index-Csjkc1MY.css">
19
+ <link rel="stylesheet" crossorigin href="/assets/index-CMISAOFr.css">
20
20
  </head>
21
21
  <body class="font-sans antialiased">
22
22
  <div id="root"></div>
@@ -442,8 +442,8 @@ export class RuntimeBridge {
442
442
  return ok;
443
443
  }
444
444
 
445
- async reloadConfig() {
446
- return this.#runtime.reloadConfig?.();
445
+ async reloadConfig(options = {}) {
446
+ return this.#runtime.reloadConfig?.(options);
447
447
  }
448
448
 
449
449
  handleApproval(id, approved) {
@@ -20,6 +20,41 @@ const GENERAL_PROJECT_DIR = (() => {
20
20
  return path.join(base, 'workspace');
21
21
  })();
22
22
 
23
+ async function listProjectRoots() {
24
+ if (process.platform === 'win32') {
25
+ const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
26
+ const roots = [];
27
+ await Promise.all(letters.map(async (letter) => {
28
+ const drivePath = `${letter}:\\`;
29
+ try {
30
+ await fs.access(drivePath);
31
+ roots.push({ name: `${letter}:`, path: drivePath, isGit: false, isDrive: true });
32
+ } catch {}
33
+ }));
34
+ return roots.sort((a, b) => a.name.localeCompare(b.name));
35
+ }
36
+
37
+ const candidates = [
38
+ { name: '/', path: path.resolve('/') },
39
+ { name: 'Home', path: process.env.HOME || process.env.USERPROFILE || '' },
40
+ { name: 'Current', path: process.cwd() },
41
+ ];
42
+ const seen = new Set();
43
+ const roots = [];
44
+ for (const candidate of candidates) {
45
+ if (!candidate.path) continue;
46
+ const resolved = path.resolve(candidate.path);
47
+ if (seen.has(resolved)) continue;
48
+ try {
49
+ const stat = await fs.stat(resolved);
50
+ if (!stat.isDirectory()) continue;
51
+ seen.add(resolved);
52
+ roots.push({ name: candidate.name, path: resolved, isGit: false, isDrive: false });
53
+ } catch {}
54
+ }
55
+ return roots;
56
+ }
57
+
23
58
  function isGeneralProjectDir(value) {
24
59
  if (!value) return false;
25
60
  return path.resolve(value) === path.resolve(GENERAL_PROJECT_DIR);
@@ -725,14 +760,19 @@ async function main() {
725
760
  jsonResponse(res, { error: true, message: err.message }, 400);
726
761
  }
727
762
  return;
728
- }
729
- if (req.method === 'POST' && url.pathname === '/api/project/browse') {
730
- const { dir } = await readBody(req);
731
- const base = dir ? path.resolve(dir) : path.resolve('/');
732
- try {
733
- const entries = await fs.readdir(base, { withFileTypes: true });
734
- const dirs = entries
735
- .filter(e => e.isDirectory() && !e.name.startsWith('.'))
763
+ }
764
+ if (req.method === 'POST' && url.pathname === '/api/project/browse') {
765
+ const { dir } = await readBody(req);
766
+ const roots = await listProjectRoots();
767
+ if (!dir && roots.length) {
768
+ jsonResponse(res, { path: '', roots, dirs: [] });
769
+ return;
770
+ }
771
+ const base = dir ? path.resolve(dir) : path.resolve('/');
772
+ try {
773
+ const entries = await fs.readdir(base, { withFileTypes: true });
774
+ const dirs = entries
775
+ .filter(e => e.isDirectory() && !e.name.startsWith('.'))
736
776
  .sort((a, b) => a.name.localeCompare(b.name))
737
777
  .map(e => ({
738
778
  name: e.name,
@@ -741,14 +781,14 @@ async function main() {
741
781
  }));
742
782
  // Check for .git directories asynchronously
743
783
  await Promise.all(dirs.map(async (d) => {
744
- try { await fs.access(path.join(d.path, '.git')); d.isGit = true; } catch {}
745
- }));
746
- jsonResponse(res, { path: base, dirs });
747
- } catch (err) {
748
- jsonResponse(res, { path: base, dirs: [], error: err.message });
749
- }
750
- return;
751
- }
784
+ try { await fs.access(path.join(d.path, '.git')); d.isGit = true; } catch {}
785
+ }));
786
+ jsonResponse(res, { path: base, roots, dirs });
787
+ } catch (err) {
788
+ jsonResponse(res, { path: base, roots, dirs: [], error: err.message });
789
+ }
790
+ return;
791
+ }
752
792
 
753
793
  // ── Config management ──
754
794
  if (req.method === 'GET' && url.pathname === '/api/config/status') {
@@ -767,7 +807,9 @@ async function main() {
767
807
  try {
768
808
  await setConfigValue(key, value);
769
809
  const config = await loadConfig();
770
- await bridge.reloadConfig();
810
+ await bridge.reloadConfig(
811
+ key === 'model.name' ? { model: config.model?.name } : {}
812
+ );
771
813
  bridge.broadcastRuntimeState();
772
814
  jsonResponse(res, { ok: true, config });
773
815
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codemini-cli",
3
- "version": "0.5.7",
3
+ "version": "0.5.8",
4
4
  "description": "Coding CLI optimized for small-model workflows and Windows PowerShell",
5
5
  "keywords": [
6
6
  "cli",
@@ -4242,6 +4242,21 @@ export async function createChatRuntime({
4242
4242
  if (hasPendingPlanApproval(currentSession)) {
4243
4243
  executionMode = 'plan';
4244
4244
  }
4245
+ const syncRuntimeFromConfig = async ({ model: nextModel } = {}) => {
4246
+ const configuredMode = String(config.execution?.mode || 'normal');
4247
+ executionMode = hasPendingPlanApproval(currentSession)
4248
+ ? 'plan'
4249
+ : (['normal', 'auto', 'plan'].includes(configuredMode) ? configuredMode : 'normal');
4250
+
4251
+ const resolvedModel = String(nextModel || '').trim();
4252
+ if (resolvedModel) {
4253
+ model = resolvedModel;
4254
+ if (currentSession && typeof currentSession === 'object') {
4255
+ currentSession.model = model;
4256
+ await saveSession(currentSession).catch(() => {});
4257
+ }
4258
+ }
4259
+ };
4245
4260
  const commands = await loadCommandsAndSkills();
4246
4261
  const reloadCommandsAndSkills = async () => {
4247
4262
  const next = await loadCommandsAndSkills();
@@ -5708,6 +5723,9 @@ export async function createChatRuntime({
5708
5723
  if (!key || !value) return { type: 'system', text: 'Usage: /config set <key> <value>' };
5709
5724
  await setConfigValue(key, value);
5710
5725
  config = await loadConfig();
5726
+ await syncRuntimeFromConfig(
5727
+ key === 'model.name' ? { model: config.model?.name } : {}
5728
+ );
5711
5729
  const text = `Set ${key}=${value}`;
5712
5730
  await persistLocalExchange(line, text);
5713
5731
  return { type: 'system', text };
@@ -5716,6 +5734,7 @@ export async function createChatRuntime({
5716
5734
  if (sub === 'reset') {
5717
5735
  await resetConfig();
5718
5736
  config = await loadConfig();
5737
+ await syncRuntimeFromConfig({ model: resolveDefaultModel(config) });
5719
5738
  compactState.threshold = 60;
5720
5739
  compactState.mode = 'conservative';
5721
5740
  compactState.autoEnabled = true;
@@ -6135,8 +6154,9 @@ export async function createChatRuntime({
6135
6154
  getCurrentSessionId: () => currentSession.id,
6136
6155
  getSessionMessages: () => currentSession.messages || [],
6137
6156
  getSessionCompact: () => currentSession.compact || null,
6138
- reloadConfig: async () => {
6157
+ reloadConfig: async (options = {}) => {
6139
6158
  config = await loadConfig();
6159
+ await syncRuntimeFromConfig(options);
6140
6160
  return config;
6141
6161
  },
6142
6162
  setExecutionMode: async (next) => {
@@ -12,6 +12,72 @@ function extractTextContent(content) {
12
12
  return '';
13
13
  }
14
14
 
15
+ function cloneAnthropicContentBlock(block) {
16
+ if (!block || typeof block !== 'object') return null;
17
+ if (block.type === 'thinking') {
18
+ const thinking = String(block.thinking || block.text || '');
19
+ if (!thinking) return null;
20
+ return {
21
+ type: 'thinking',
22
+ thinking,
23
+ ...(block.signature ? { signature: String(block.signature) } : {})
24
+ };
25
+ }
26
+ if (block.type === 'redacted_thinking') {
27
+ const data = block.data != null ? String(block.data) : '';
28
+ if (!data) return null;
29
+ return { type: 'redacted_thinking', data };
30
+ }
31
+ if (block.type === 'text') {
32
+ const text = String(block.text || '');
33
+ return text ? { type: 'text', text } : null;
34
+ }
35
+ if (block.type === 'tool_use') {
36
+ const name = String(block.name || '').trim();
37
+ if (!name) return null;
38
+ return {
39
+ type: 'tool_use',
40
+ id: String(block.id || ''),
41
+ name,
42
+ input: block.input && typeof block.input === 'object' && !Array.isArray(block.input) ? block.input : {}
43
+ };
44
+ }
45
+ return null;
46
+ }
47
+
48
+ function extractThinkingBlocks(message) {
49
+ const source = [
50
+ ...(Array.isArray(message?.reasoning_details) ? message.reasoning_details : []),
51
+ ...(Array.isArray(message?.content) ? message.content : [])
52
+ ];
53
+ return source
54
+ .filter((block) => block?.type === 'thinking' || block?.type === 'redacted_thinking')
55
+ .map(cloneAnthropicContentBlock)
56
+ .filter(Boolean);
57
+ }
58
+
59
+ function buildAssistantMessage({ text = '', toolCalls = [], thinkingBlocks = [] }) {
60
+ const assistantMessage = {
61
+ role: 'assistant',
62
+ content: text
63
+ };
64
+ const reasoningDetails = Array.isArray(thinkingBlocks)
65
+ ? thinkingBlocks.map(cloneAnthropicContentBlock).filter(Boolean)
66
+ : [];
67
+ if (reasoningDetails.length > 0) assistantMessage.reasoning_details = reasoningDetails;
68
+ if (Array.isArray(toolCalls) && toolCalls.length > 0) {
69
+ assistantMessage.tool_calls = toolCalls.map((tc) => ({
70
+ id: tc.id,
71
+ type: 'function',
72
+ function: {
73
+ name: tc.name,
74
+ arguments: tc.arguments || '{}'
75
+ }
76
+ }));
77
+ }
78
+ return assistantMessage;
79
+ }
80
+
15
81
  function normalizeIncomingToolCallArguments(argumentsValue) {
16
82
  if (typeof argumentsValue === 'string') return argumentsValue;
17
83
  if (argumentsValue == null) return '{}';
@@ -41,7 +107,8 @@ function normalizeMessages(messages) {
41
107
  const systemParts = [];
42
108
  const out = [];
43
109
 
44
- for (const message of source) {
110
+ for (let i = 0; i < source.length; i += 1) {
111
+ const message = source[i];
45
112
  if (!message || typeof message !== 'object') continue;
46
113
  if (message.role === 'system') {
47
114
  const text = extractTextContent(message.content);
@@ -50,35 +117,45 @@ function normalizeMessages(messages) {
50
117
  }
51
118
 
52
119
  if (message.role === 'tool') {
120
+ const toolResults = [];
121
+ while (i < source.length) {
122
+ const toolMessage = source[i];
123
+ if (!toolMessage || typeof toolMessage !== 'object' || toolMessage.role !== 'tool') break;
124
+ toolResults.push({
125
+ type: 'tool_result',
126
+ tool_use_id: String(toolMessage.tool_call_id || ''),
127
+ content: extractTextContent(toolMessage.content)
128
+ });
129
+ i += 1;
130
+ }
131
+ i -= 1;
53
132
  out.push({
54
133
  role: 'user',
55
- content: [
56
- {
57
- type: 'tool_result',
58
- tool_use_id: String(message.tool_call_id || ''),
59
- content: extractTextContent(message.content)
60
- }
61
- ]
134
+ content: toolResults
62
135
  });
63
136
  continue;
64
137
  }
65
138
 
66
- const contentBlocks = [];
139
+ const contentBlocks = message.role === 'assistant' ? extractThinkingBlocks(message) : [];
67
140
  const text = extractTextContent(message.content);
68
141
  if (text) {
69
142
  contentBlocks.push({ type: 'text', text });
70
143
  }
71
144
 
145
+ const hasContentToolUse = Array.isArray(message.content)
146
+ && message.content.some((block) => block?.type === 'tool_use');
72
147
  if (message.role === 'assistant' && Array.isArray(message.tool_calls)) {
73
- for (const toolCall of message.tool_calls) {
74
- const name = String(toolCall?.function?.name || toolCall?.name || '').trim();
75
- if (!name) continue;
76
- contentBlocks.push({
77
- type: 'tool_use',
78
- id: String(toolCall?.id || ''),
79
- name,
80
- input: tryParseJsonObject(toolCall?.function?.arguments ?? toolCall?.arguments)
81
- });
148
+ if (!hasContentToolUse) {
149
+ for (const toolCall of message.tool_calls) {
150
+ const name = String(toolCall?.function?.name || toolCall?.name || '').trim();
151
+ if (!name) continue;
152
+ contentBlocks.push({
153
+ type: 'tool_use',
154
+ id: String(toolCall?.id || ''),
155
+ name,
156
+ input: tryParseJsonObject(toolCall?.function?.arguments ?? toolCall?.arguments)
157
+ });
158
+ }
82
159
  }
83
160
  }
84
161
 
@@ -142,6 +219,10 @@ function hasTrailingToolContext(messages) {
142
219
 
143
220
  function extractAssistantResult(data, messages) {
144
221
  const content = Array.isArray(data?.content) ? data.content : [];
222
+ const thinkingBlocks = content
223
+ .filter((block) => block?.type === 'thinking' || block?.type === 'redacted_thinking')
224
+ .map(cloneAnthropicContentBlock)
225
+ .filter(Boolean);
145
226
  const text = content
146
227
  .filter((block) => block?.type === 'text')
147
228
  .map((block) => block.text || '')
@@ -163,7 +244,8 @@ function extractAssistantResult(data, messages) {
163
244
  toolCalls: [],
164
245
  usage: data?.usage || null,
165
246
  incomplete: true,
166
- content
247
+ content,
248
+ assistantMessage: buildAssistantMessage({ text: '', toolCalls: [], thinkingBlocks })
167
249
  };
168
250
  }
169
251
  throw new Error('Anthropic gateway returned empty assistant response');
@@ -173,7 +255,8 @@ function extractAssistantResult(data, messages) {
173
255
  text,
174
256
  toolCalls,
175
257
  usage: data?.usage || null,
176
- content
258
+ content,
259
+ assistantMessage: buildAssistantMessage({ text, toolCalls, thinkingBlocks })
177
260
  };
178
261
  }
179
262
 
@@ -214,7 +297,7 @@ function emptyToolCall(index) {
214
297
  };
215
298
  }
216
299
 
217
- function buildFinalStreamResult(text, toolCallsByIndex, usage, messages) {
300
+ function buildFinalStreamResult(text, toolCallsByIndex, usage, messages, thinkingBlocks = []) {
218
301
  const toolCalls = Array.from(toolCallsByIndex.entries())
219
302
  .sort((a, b) => a[0] - b[0])
220
303
  .map(([, tc], i) => ({
@@ -225,6 +308,10 @@ function buildFinalStreamResult(text, toolCallsByIndex, usage, messages) {
225
308
  .filter((tc) => tc.name);
226
309
  const normalizedText = String(text || '').trim();
227
310
  const content = [];
311
+ for (const block of thinkingBlocks) {
312
+ const cloned = cloneAnthropicContentBlock(block);
313
+ if (cloned) content.push(cloned);
314
+ }
228
315
  if (text) content.push({ type: 'text', text });
229
316
  for (const toolCall of toolCalls) {
230
317
  content.push({
@@ -242,7 +329,8 @@ function buildFinalStreamResult(text, toolCallsByIndex, usage, messages) {
242
329
  toolCalls: [],
243
330
  usage,
244
331
  incomplete: true,
245
- content: []
332
+ content: [],
333
+ assistantMessage: buildAssistantMessage({ text: '', toolCalls: [], thinkingBlocks })
246
334
  };
247
335
  }
248
336
  throw new Error('Anthropic gateway stream returned empty assistant response');
@@ -253,7 +341,8 @@ function buildFinalStreamResult(text, toolCallsByIndex, usage, messages) {
253
341
  toolCalls,
254
342
  usage,
255
343
  incomplete: false,
256
- content
344
+ content,
345
+ assistantMessage: buildAssistantMessage({ text, toolCalls, thinkingBlocks })
257
346
  };
258
347
  }
259
348
 
@@ -349,6 +438,7 @@ export async function createChatCompletionStream({
349
438
  let text = '';
350
439
  let usage = null;
351
440
  const toolCallsByIndex = new Map();
441
+ const thinkingBlocksByIndex = new Map();
352
442
 
353
443
  for await (const chunk of iterateSseEvents(response.body)) {
354
444
  usage = mergeUsage(usage, chunk?.data?.usage);
@@ -366,6 +456,10 @@ export async function createChatCompletionStream({
366
456
  : '';
367
457
  current.arguments = current.arguments || initialInput;
368
458
  toolCallsByIndex.set(index, current);
459
+ } else if (contentBlock.type === 'thinking' || contentBlock.type === 'redacted_thinking') {
460
+ const current = cloneAnthropicContentBlock(contentBlock) || { type: contentBlock.type };
461
+ if (current.type === 'thinking' && current.thinking == null) current.thinking = '';
462
+ thinkingBlocksByIndex.set(index, current);
369
463
  }
370
464
  continue;
371
465
  }
@@ -382,6 +476,20 @@ export async function createChatCompletionStream({
382
476
  continue;
383
477
  }
384
478
 
479
+ if (delta.type === 'thinking_delta') {
480
+ const current = thinkingBlocksByIndex.get(index) || { type: 'thinking', thinking: '' };
481
+ current.thinking = `${current.thinking || ''}${String(delta.thinking || '')}`;
482
+ thinkingBlocksByIndex.set(index, current);
483
+ continue;
484
+ }
485
+
486
+ if (delta.type === 'signature_delta') {
487
+ const current = thinkingBlocksByIndex.get(index) || { type: 'thinking', thinking: '' };
488
+ current.signature = String(delta.signature || '');
489
+ thinkingBlocksByIndex.set(index, current);
490
+ continue;
491
+ }
492
+
385
493
  if (delta.type === 'input_json_delta') {
386
494
  const current = toolCallsByIndex.get(index) || emptyToolCall(index);
387
495
  current.arguments = `${current.arguments || ''}${String(delta.partial_json || '')}`;
@@ -397,5 +505,10 @@ export async function createChatCompletionStream({
397
505
  }
398
506
  }
399
507
 
400
- return buildFinalStreamResult(text, toolCallsByIndex, usage, messages);
508
+ const thinkingBlocks = Array.from(thinkingBlocksByIndex.entries())
509
+ .sort((a, b) => a[0] - b[0])
510
+ .map(([, block]) => cloneAnthropicContentBlock(block))
511
+ .filter(Boolean);
512
+
513
+ return buildFinalStreamResult(text, toolCallsByIndex, usage, messages, thinkingBlocks);
401
514
  }