codemini-cli 0.5.6 → 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-BqNKEgHB.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-dYs_njBc.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) {
@@ -15,11 +15,52 @@ import { getReplyLanguage } from '../src/core/reply-language.js';
15
15
  import { getSkillsDir, getBaseConfigDir } from '../src/core/paths.js';
16
16
  import { VERSION } from '../src/core/version.js';
17
17
 
18
- const GENERAL_PROJECT_DIR = (() => {
19
- const base = getBaseConfigDir();
20
- return path.join(base, 'workspace');
21
- })();
22
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
+ const GENERAL_PROJECT_DIR = (() => {
19
+ const base = getBaseConfigDir();
20
+ return path.join(base, 'workspace');
21
+ })();
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
+
58
+ function isGeneralProjectDir(value) {
59
+ if (!value) return false;
60
+ return path.resolve(value) === path.resolve(GENERAL_PROJECT_DIR);
61
+ }
62
+
63
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
23
64
  const CLIENT_SOURCE_DIR = path.join(__dirname, 'client');
24
65
  let CLIENT_DIR = CLIENT_SOURCE_DIR;
25
66
  try {
@@ -254,7 +295,7 @@ async function buildRuntimeForSession({ sessionId, model, projectDir }) {
254
295
  model: model || config.model?.name,
255
296
  systemPrompt
256
297
  });
257
- return { runtime, config, session, cwd: process.cwd(), isGeneral: process.cwd() === GENERAL_PROJECT_DIR };
298
+ return { runtime, config, session, cwd: process.cwd(), isGeneral: isGeneralProjectDir(process.cwd()) };
258
299
  }
259
300
 
260
301
  async function main() {
@@ -376,7 +417,7 @@ async function main() {
376
417
 
377
418
  // ── Runtime state ──
378
419
  if (req.method === 'GET' && url.pathname === '/api/state') {
379
- jsonResponse(res, { ...bridge.getState(), cwd: currentProjectDir, isGeneral: currentProjectDir === GENERAL_PROJECT_DIR });
420
+ jsonResponse(res, { ...bridge.getState(), cwd: currentProjectDir, isGeneral: isGeneralProjectDir(currentProjectDir) });
380
421
  return;
381
422
  }
382
423
  if (req.method === 'GET' && url.pathname === '/api/completions') {
@@ -532,7 +573,7 @@ async function main() {
532
573
  // ── Session management ──
533
574
  if (req.method === 'GET' && url.pathname === '/api/sessions') {
534
575
  const sessions = await listSessions(1000);
535
- const enriched = sessions.map(s => ({ ...s, isGeneral: s.projectDir === GENERAL_PROJECT_DIR }));
576
+ const enriched = sessions.map(s => ({ ...s, isGeneral: isGeneralProjectDir(s.projectDir) }));
536
577
  jsonResponse(res, enriched);
537
578
  return;
538
579
  }
@@ -545,7 +586,7 @@ async function main() {
545
586
  reused: true,
546
587
  sessionId: bridge.getSessionId(),
547
588
  cwd: currentProjectDir,
548
- isGeneral: currentProjectDir === GENERAL_PROJECT_DIR
589
+ isGeneral: isGeneralProjectDir(currentProjectDir)
549
590
  });
550
591
  return;
551
592
  }
@@ -555,7 +596,7 @@ async function main() {
555
596
  });
556
597
  await bridge.switchRuntime(newRuntime);
557
598
  currentProjectDir = process.cwd();
558
- jsonResponse(res, { ok: true, sessionId: session.id, cwd: currentProjectDir, isGeneral: currentProjectDir === GENERAL_PROJECT_DIR });
599
+ jsonResponse(res, { ok: true, sessionId: session.id, cwd: currentProjectDir, isGeneral: isGeneralProjectDir(currentProjectDir) });
559
600
  } catch (err) {
560
601
  jsonResponse(res, { error: true, message: err.message }, 500);
561
602
  }
@@ -571,7 +612,7 @@ async function main() {
571
612
  });
572
613
  await bridge.switchRuntime(newRuntime);
573
614
  currentProjectDir = process.cwd();
574
- jsonResponse(res, { ok: true, sessionId, cwd: currentProjectDir, isGeneral: currentProjectDir === GENERAL_PROJECT_DIR });
615
+ jsonResponse(res, { ok: true, sessionId, cwd: currentProjectDir, isGeneral: isGeneralProjectDir(currentProjectDir) });
575
616
  } catch (err) {
576
617
  jsonResponse(res, { error: true, message: err.message }, 500);
577
618
  }
@@ -601,7 +642,7 @@ async function main() {
601
642
  nextSessionId = built.session.id;
602
643
  cwd = currentProjectDir;
603
644
  }
604
- jsonResponse(res, { ok: true, removed: result.removed, sessionId: nextSessionId, cwd, isGeneral: currentProjectDir === GENERAL_PROJECT_DIR });
645
+ jsonResponse(res, { ok: true, removed: result.removed, sessionId: nextSessionId, cwd, isGeneral: isGeneralProjectDir(currentProjectDir) });
605
646
  } catch (err) {
606
647
  jsonResponse(res, { error: true, message: err.message }, 500);
607
648
  }
@@ -610,7 +651,7 @@ async function main() {
610
651
 
611
652
  // ── Project management ──
612
653
  if (req.method === 'GET' && url.pathname === '/api/project') {
613
- jsonResponse(res, { cwd: currentProjectDir, isGeneral: currentProjectDir === GENERAL_PROJECT_DIR });
654
+ jsonResponse(res, { cwd: currentProjectDir, isGeneral: isGeneralProjectDir(currentProjectDir) });
614
655
  return;
615
656
  }
616
657
  if (req.method === 'GET' && url.pathname === '/api/git') {
@@ -685,36 +726,53 @@ async function main() {
685
726
  jsonResponse(res, result);
686
727
  return;
687
728
  }
688
- if (req.method === 'POST' && url.pathname === '/api/project/open') {
689
- const { path: projectPath } = await readBody(req);
690
- if (!projectPath) { jsonResponse(res, { error: true, message: 'Missing path' }, 400); return; }
691
- try {
692
- // Client marker for general workspace
693
- const resolved = projectPath === '__codemini_general__'
694
- ? GENERAL_PROJECT_DIR
695
- : path.resolve(projectPath);
696
- const stat = await fs.stat(resolved);
697
- if (!stat.isDirectory()) throw new Error('Not a directory');
698
- process.chdir(resolved);
699
- currentProjectDir = process.cwd();
700
- // Re-init runtime in new project
701
- const { runtime: newRuntime, session } = await buildRuntimeForSession({
702
- model: bridge.getState().model
703
- });
704
- await bridge.switchRuntime(newRuntime);
705
- jsonResponse(res, { ok: true, cwd: currentProjectDir, sessionId: session.id, isGeneral: currentProjectDir === GENERAL_PROJECT_DIR });
706
- } catch (err) {
707
- jsonResponse(res, { error: true, message: err.message }, 400);
708
- }
729
+ if (req.method === 'POST' && url.pathname === '/api/project/open') {
730
+ const { path: projectPath } = await readBody(req);
731
+ if (!projectPath) { jsonResponse(res, { error: true, message: 'Missing path' }, 400); return; }
732
+ try {
733
+ // Client marker for general workspace
734
+ const openingGeneral = projectPath === '__codemini_general__';
735
+ const resolved = openingGeneral ? GENERAL_PROJECT_DIR : path.resolve(projectPath);
736
+ const stat = await fs.stat(resolved);
737
+ if (!stat.isDirectory()) throw new Error('Not a directory');
738
+ let built;
739
+ if (openingGeneral) {
740
+ const all = await listSessions(1000, { includeEmpty: true });
741
+ const reusable = all.find((session) =>
742
+ isGeneralProjectDir(session.projectDir) &&
743
+ Number(session.messageCount || 0) === 0
744
+ );
745
+ built = reusable
746
+ ? await buildRuntimeForSession({ sessionId: reusable.id, model: bridge.getState().model })
747
+ : await buildRuntimeForSession({ model: bridge.getState().model, projectDir: GENERAL_PROJECT_DIR });
748
+ } else {
749
+ process.chdir(resolved);
750
+ currentProjectDir = process.cwd();
751
+ built = await buildRuntimeForSession({
752
+ model: bridge.getState().model
753
+ });
754
+ }
755
+ const { runtime: newRuntime, session } = built;
756
+ await bridge.switchRuntime(newRuntime);
757
+ currentProjectDir = process.cwd();
758
+ jsonResponse(res, { ok: true, cwd: currentProjectDir, sessionId: session.id, isGeneral: isGeneralProjectDir(currentProjectDir) });
759
+ } catch (err) {
760
+ jsonResponse(res, { error: true, message: err.message }, 400);
761
+ }
709
762
  return;
710
- }
711
- if (req.method === 'POST' && url.pathname === '/api/project/browse') {
712
- const { dir } = await readBody(req);
713
- const base = dir ? path.resolve(dir) : path.resolve('/');
714
- try {
715
- const entries = await fs.readdir(base, { withFileTypes: true });
716
- const dirs = entries
717
- .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('.'))
718
776
  .sort((a, b) => a.name.localeCompare(b.name))
719
777
  .map(e => ({
720
778
  name: e.name,
@@ -723,14 +781,14 @@ async function main() {
723
781
  }));
724
782
  // Check for .git directories asynchronously
725
783
  await Promise.all(dirs.map(async (d) => {
726
- try { await fs.access(path.join(d.path, '.git')); d.isGit = true; } catch {}
727
- }));
728
- jsonResponse(res, { path: base, dirs });
729
- } catch (err) {
730
- jsonResponse(res, { path: base, dirs: [], error: err.message });
731
- }
732
- return;
733
- }
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
+ }
734
792
 
735
793
  // ── Config management ──
736
794
  if (req.method === 'GET' && url.pathname === '/api/config/status') {
@@ -749,7 +807,9 @@ async function main() {
749
807
  try {
750
808
  await setConfigValue(key, value);
751
809
  const config = await loadConfig();
752
- await bridge.reloadConfig();
810
+ await bridge.reloadConfig(
811
+ key === 'model.name' ? { model: config.model?.name } : {}
812
+ );
753
813
  bridge.broadcastRuntimeState();
754
814
  jsonResponse(res, { ok: true, config });
755
815
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codemini-cli",
3
- "version": "0.5.6",
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
  }