codemini-cli 0.4.1 → 0.4.3
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.
- package/OPERATIONS.md +4 -2
- package/README.md +87 -7
- package/deployment.md +14 -7
- package/package.json +1 -3
- package/skills/grill-me/SKILL.md +30 -0
- package/skills/project-requirements/SKILL.md +245 -0
- package/skills/superpowers-lite/SKILL.md +5 -1
- package/src/cli.js +1 -1
- package/src/commands/run.js +5 -4
- package/src/commands/skill.js +145 -53
- package/src/core/agent-loop.js +9 -214
- package/src/core/chat-runtime.js +520 -78
- package/src/core/command-loader.js +12 -5
- package/src/core/config-store.js +6 -3
- package/src/core/context-compact.js +2 -1
- package/src/core/dream-audit.js +12 -0
- package/src/core/dream-consolidate.js +131 -59
- package/src/core/dream-evaluator.js +86 -0
- package/src/core/fff-adapter.js +1 -1
- package/src/core/memory-store.js +145 -10
- package/src/core/provider/anthropic.js +2 -2
- package/src/core/provider/openai-compatible.js +2 -2
- package/src/core/reflect-skill.js +178 -0
- package/src/core/shell.js +1 -1
- package/src/core/tool-result-store.js +206 -0
- package/src/core/tools.js +242 -69
- package/src/tui/chat-app.js +298 -48
- package/src/tui/tool-activity/presenters/system.js +6 -0
- package/src/core/provider/anthropic.sdk-backup.js +0 -439
- package/src/core/provider/openai-compatible.sdk-backup.js +0 -412
|
@@ -10,5 +10,11 @@ export function describeSystemToolActivity(copy, parsed, { done = false, blocked
|
|
|
10
10
|
if (blocked) return makeBlocked(copy, safeTarget);
|
|
11
11
|
return done ? `${copy.toolActivity.doneFileIndex}: ${safeTarget}` : `${copy.toolActivity.doingFileIndex}: ${safeTarget}`;
|
|
12
12
|
}
|
|
13
|
+
if (parsed.base === 'prompt_budget') {
|
|
14
|
+
if (blocked) return makeBlocked(copy, 'prompt_budget');
|
|
15
|
+
return done
|
|
16
|
+
? (copy.toolActivity.donePromptBudget || 'Prompt budget measured')
|
|
17
|
+
: (copy.toolActivity.doingPromptBudget || 'Measuring prompt budget');
|
|
18
|
+
}
|
|
13
19
|
return '';
|
|
14
20
|
}
|
|
@@ -1,439 +0,0 @@
|
|
|
1
|
-
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
-
|
|
3
|
-
function extractTextContent(content) {
|
|
4
|
-
if (typeof content === 'string') return content;
|
|
5
|
-
if (Array.isArray(content)) {
|
|
6
|
-
return content
|
|
7
|
-
.map((part) => {
|
|
8
|
-
if (typeof part === 'string') return part;
|
|
9
|
-
if (part?.type === 'text') return part.text || '';
|
|
10
|
-
return '';
|
|
11
|
-
})
|
|
12
|
-
.join('');
|
|
13
|
-
}
|
|
14
|
-
return '';
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function normalizeIncomingToolCallArguments(argumentsValue) {
|
|
18
|
-
if (typeof argumentsValue === 'string') return argumentsValue;
|
|
19
|
-
if (argumentsValue == null) return '{}';
|
|
20
|
-
try {
|
|
21
|
-
return JSON.stringify(argumentsValue);
|
|
22
|
-
} catch {
|
|
23
|
-
return '{}';
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function tryParseJsonObject(raw) {
|
|
28
|
-
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
29
|
-
return raw;
|
|
30
|
-
}
|
|
31
|
-
if (typeof raw !== 'string') return {};
|
|
32
|
-
try {
|
|
33
|
-
const parsed = JSON.parse(raw);
|
|
34
|
-
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
35
|
-
return parsed;
|
|
36
|
-
}
|
|
37
|
-
} catch {}
|
|
38
|
-
return {};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function normalizeAssistantContentBlocks(message) {
|
|
42
|
-
if (Array.isArray(message?.content) && message.content.length > 0) {
|
|
43
|
-
return message.content.map((block) => ({ ...block }));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const contentBlocks = [];
|
|
47
|
-
const text = extractTextContent(message?.content);
|
|
48
|
-
if (text) {
|
|
49
|
-
contentBlocks.push({ type: 'text', text });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (Array.isArray(message?.tool_calls)) {
|
|
53
|
-
for (const toolCall of message.tool_calls) {
|
|
54
|
-
const name = String(toolCall?.function?.name || toolCall?.name || '').trim();
|
|
55
|
-
if (!name) continue;
|
|
56
|
-
contentBlocks.push({
|
|
57
|
-
type: 'tool_use',
|
|
58
|
-
id: String(toolCall?.id || ''),
|
|
59
|
-
name,
|
|
60
|
-
input: tryParseJsonObject(toolCall?.function?.arguments ?? toolCall?.arguments)
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return contentBlocks;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function normalizeMessages(messages) {
|
|
69
|
-
const source = Array.isArray(messages) ? messages : [];
|
|
70
|
-
const systemParts = [];
|
|
71
|
-
const out = [];
|
|
72
|
-
|
|
73
|
-
for (const message of source) {
|
|
74
|
-
if (!message || typeof message !== 'object') continue;
|
|
75
|
-
if (message.role === 'system') {
|
|
76
|
-
const text = extractTextContent(message.content);
|
|
77
|
-
if (text) systemParts.push(text);
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (message.role === 'tool') {
|
|
82
|
-
out.push({
|
|
83
|
-
role: 'user',
|
|
84
|
-
content: [
|
|
85
|
-
{
|
|
86
|
-
type: 'tool_result',
|
|
87
|
-
tool_use_id: String(message.tool_call_id || ''),
|
|
88
|
-
content: extractTextContent(message.content)
|
|
89
|
-
}
|
|
90
|
-
]
|
|
91
|
-
});
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (message.role === 'assistant') {
|
|
96
|
-
out.push({
|
|
97
|
-
role: 'assistant',
|
|
98
|
-
content: normalizeAssistantContentBlocks(message)
|
|
99
|
-
});
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
out.push({
|
|
104
|
-
role: message.role,
|
|
105
|
-
content: [{ type: 'text', text: extractTextContent(message.content) }]
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
system: systemParts.join('\n\n').trim() || undefined,
|
|
111
|
-
messages: out
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function normalizeTools(tools) {
|
|
116
|
-
const source = Array.isArray(tools) ? tools : [];
|
|
117
|
-
return source
|
|
118
|
-
.map((tool) => {
|
|
119
|
-
const fn = tool?.function || {};
|
|
120
|
-
const name = String(fn.name || '').trim();
|
|
121
|
-
if (!name) return null;
|
|
122
|
-
return {
|
|
123
|
-
name,
|
|
124
|
-
...(fn.description ? { description: String(fn.description) } : {}),
|
|
125
|
-
input_schema: fn.parameters && typeof fn.parameters === 'object' ? fn.parameters : { type: 'object' }
|
|
126
|
-
};
|
|
127
|
-
})
|
|
128
|
-
.filter(Boolean);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function buildPayload({ model, temperature, messages, tools, stream = false, maxTokens = 4096 }) {
|
|
132
|
-
const normalized = normalizeMessages(messages);
|
|
133
|
-
const payload = {
|
|
134
|
-
model,
|
|
135
|
-
max_tokens: maxTokens,
|
|
136
|
-
temperature,
|
|
137
|
-
messages: normalized.messages
|
|
138
|
-
};
|
|
139
|
-
if (normalized.system) payload.system = normalized.system;
|
|
140
|
-
if (stream) payload.stream = true;
|
|
141
|
-
|
|
142
|
-
const normalizedTools = normalizeTools(tools);
|
|
143
|
-
if (normalizedTools.length > 0) {
|
|
144
|
-
payload.tools = normalizedTools;
|
|
145
|
-
payload.tool_choice = { type: 'auto' };
|
|
146
|
-
}
|
|
147
|
-
return payload;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function hasTrailingToolContext(messages) {
|
|
151
|
-
const source = Array.isArray(messages) ? messages : [];
|
|
152
|
-
for (let index = source.length - 1; index >= 0; index -= 1) {
|
|
153
|
-
const message = source[index];
|
|
154
|
-
if (!message || typeof message !== 'object') continue;
|
|
155
|
-
if (message.role === 'tool') return true;
|
|
156
|
-
if (message.role === 'assistant' || message.role === 'user') return false;
|
|
157
|
-
}
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function buildAssistantMessage(content) {
|
|
162
|
-
return {
|
|
163
|
-
role: 'assistant',
|
|
164
|
-
content
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function extractAssistantResult(data, messages) {
|
|
169
|
-
const content = Array.isArray(data?.content) ? data.content.map((block) => ({ ...block })) : [];
|
|
170
|
-
const text = content
|
|
171
|
-
.filter((block) => block?.type === 'text')
|
|
172
|
-
.map((block) => block.text || '')
|
|
173
|
-
.join('');
|
|
174
|
-
const toolCalls = content
|
|
175
|
-
.filter((block) => block?.type === 'tool_use')
|
|
176
|
-
.map((block) => ({
|
|
177
|
-
id: String(block.id || ''),
|
|
178
|
-
name: String(block.name || ''),
|
|
179
|
-
arguments: normalizeIncomingToolCallArguments(block.input)
|
|
180
|
-
}))
|
|
181
|
-
.filter((toolCall) => toolCall.name);
|
|
182
|
-
const normalizedText = String(text || '').trim();
|
|
183
|
-
|
|
184
|
-
if (!normalizedText && toolCalls.length === 0) {
|
|
185
|
-
if (hasTrailingToolContext(messages)) {
|
|
186
|
-
return {
|
|
187
|
-
text: '',
|
|
188
|
-
toolCalls: [],
|
|
189
|
-
usage: data?.usage || null,
|
|
190
|
-
incomplete: true,
|
|
191
|
-
content
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
throw new Error('Anthropic gateway returned empty assistant response');
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return {
|
|
198
|
-
text,
|
|
199
|
-
toolCalls,
|
|
200
|
-
usage: data?.usage || null,
|
|
201
|
-
content,
|
|
202
|
-
assistantMessage: buildAssistantMessage(content)
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function mergeUsage(current, next) {
|
|
207
|
-
if (!next || typeof next !== 'object') return current;
|
|
208
|
-
return {
|
|
209
|
-
...(current || {}),
|
|
210
|
-
...next
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function emptyToolCall(index) {
|
|
215
|
-
return {
|
|
216
|
-
index,
|
|
217
|
-
id: '',
|
|
218
|
-
name: '',
|
|
219
|
-
arguments: ''
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function buildFinalStreamResult(text, toolCallsByIndex, usage, messages, contentBlocksByIndex) {
|
|
224
|
-
const toolCalls = Array.from(toolCallsByIndex.entries())
|
|
225
|
-
.sort((a, b) => a[0] - b[0])
|
|
226
|
-
.map(([, tc], i) => ({
|
|
227
|
-
id: tc.id || `tc-${i + 1}`,
|
|
228
|
-
name: tc.name,
|
|
229
|
-
arguments: tc.arguments || '{}'
|
|
230
|
-
}))
|
|
231
|
-
.filter((tc) => tc.name);
|
|
232
|
-
const normalizedText = String(text || '').trim();
|
|
233
|
-
const content = Array.from(contentBlocksByIndex.entries())
|
|
234
|
-
.sort((a, b) => a[0] - b[0])
|
|
235
|
-
.map(([, block]) => {
|
|
236
|
-
if (block.type === 'tool_use') {
|
|
237
|
-
return {
|
|
238
|
-
type: 'tool_use',
|
|
239
|
-
id: block.id,
|
|
240
|
-
name: block.name,
|
|
241
|
-
input: tryParseJsonObject(block.arguments)
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
if (block.type === 'thinking') {
|
|
245
|
-
return {
|
|
246
|
-
type: 'thinking',
|
|
247
|
-
thinking: block.thinking || '',
|
|
248
|
-
...(block.signature ? { signature: block.signature } : {})
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
return {
|
|
252
|
-
type: 'text',
|
|
253
|
-
text: block.text || ''
|
|
254
|
-
};
|
|
255
|
-
})
|
|
256
|
-
.filter((block) => {
|
|
257
|
-
if (block.type === 'tool_use') return Boolean(block.name);
|
|
258
|
-
if (block.type === 'thinking') return Boolean(block.thinking);
|
|
259
|
-
return Boolean(block.text);
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
if (!normalizedText && toolCalls.length === 0) {
|
|
263
|
-
if (hasTrailingToolContext(messages)) {
|
|
264
|
-
return {
|
|
265
|
-
text: '',
|
|
266
|
-
toolCalls: [],
|
|
267
|
-
usage,
|
|
268
|
-
incomplete: true,
|
|
269
|
-
content: []
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
throw new Error('Anthropic gateway stream returned empty assistant response');
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
text,
|
|
277
|
-
toolCalls,
|
|
278
|
-
usage,
|
|
279
|
-
incomplete: false,
|
|
280
|
-
content,
|
|
281
|
-
assistantMessage: buildAssistantMessage(content)
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function createClient({ baseUrl, apiKey, timeoutMs = 90000, maxRetries = 2 }) {
|
|
286
|
-
return new Anthropic({
|
|
287
|
-
apiKey,
|
|
288
|
-
baseURL: String(baseUrl || '').replace(/\/$/, ''),
|
|
289
|
-
timeout: timeoutMs,
|
|
290
|
-
maxRetries
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function inferEventType(event) {
|
|
295
|
-
const explicit = String(event?.type || event?.event || '').trim();
|
|
296
|
-
if (explicit) return explicit;
|
|
297
|
-
if (event?.content_block && typeof event?.index === 'number') return 'content_block_start';
|
|
298
|
-
if (event?.delta && typeof event?.index === 'number') return 'content_block_delta';
|
|
299
|
-
if (event?.message) return 'message_start';
|
|
300
|
-
if (event?.usage) return 'message_delta';
|
|
301
|
-
if (event && typeof event === 'object' && Object.keys(event).length === 0) return 'message_stop';
|
|
302
|
-
return '';
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
export async function createChatCompletion({
|
|
306
|
-
baseUrl,
|
|
307
|
-
apiKey,
|
|
308
|
-
model,
|
|
309
|
-
messages,
|
|
310
|
-
temperature = 0.2,
|
|
311
|
-
tools,
|
|
312
|
-
timeoutMs = 90000,
|
|
313
|
-
maxTokens = 4096,
|
|
314
|
-
maxRetries = 2
|
|
315
|
-
}) {
|
|
316
|
-
const client = createClient({ baseUrl, apiKey, timeoutMs, maxRetries });
|
|
317
|
-
const response = await client.messages.create(buildPayload({ model, temperature, messages, tools, maxTokens }));
|
|
318
|
-
return extractAssistantResult(response, messages);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
export async function createChatCompletionStream({
|
|
322
|
-
baseUrl,
|
|
323
|
-
apiKey,
|
|
324
|
-
model,
|
|
325
|
-
messages,
|
|
326
|
-
temperature = 0.2,
|
|
327
|
-
tools,
|
|
328
|
-
onTextDelta,
|
|
329
|
-
onToolCallDelta,
|
|
330
|
-
timeoutMs = 90000,
|
|
331
|
-
maxTokens = 4096,
|
|
332
|
-
maxRetries = 2
|
|
333
|
-
}) {
|
|
334
|
-
const client = createClient({ baseUrl, apiKey, timeoutMs, maxRetries });
|
|
335
|
-
const stream = await client.messages.create(buildPayload({ model, temperature, messages, tools, stream: true, maxTokens }));
|
|
336
|
-
|
|
337
|
-
let text = '';
|
|
338
|
-
let usage = null;
|
|
339
|
-
const toolCallsByIndex = new Map();
|
|
340
|
-
const contentBlocksByIndex = new Map();
|
|
341
|
-
|
|
342
|
-
for await (const event of stream) {
|
|
343
|
-
const eventType = inferEventType(event);
|
|
344
|
-
usage = mergeUsage(usage, event?.usage);
|
|
345
|
-
usage = mergeUsage(usage, event?.message?.usage);
|
|
346
|
-
|
|
347
|
-
if (eventType === 'content_block_start') {
|
|
348
|
-
const index = Number(event?.index ?? 0);
|
|
349
|
-
const contentBlock = event?.content_block || {};
|
|
350
|
-
if (contentBlock.type === 'tool_use') {
|
|
351
|
-
const current = toolCallsByIndex.get(index) || emptyToolCall(index);
|
|
352
|
-
current.id = String(contentBlock.id || current.id || '');
|
|
353
|
-
current.name = String(contentBlock.name || current.name || '');
|
|
354
|
-
const initialInput = contentBlock.input && Object.keys(contentBlock.input).length > 0
|
|
355
|
-
? normalizeIncomingToolCallArguments(contentBlock.input)
|
|
356
|
-
: '';
|
|
357
|
-
current.arguments = current.arguments || initialInput;
|
|
358
|
-
toolCallsByIndex.set(index, current);
|
|
359
|
-
contentBlocksByIndex.set(index, {
|
|
360
|
-
type: 'tool_use',
|
|
361
|
-
id: current.id,
|
|
362
|
-
name: current.name,
|
|
363
|
-
arguments: current.arguments
|
|
364
|
-
});
|
|
365
|
-
} else if (contentBlock.type === 'thinking') {
|
|
366
|
-
contentBlocksByIndex.set(index, {
|
|
367
|
-
type: 'thinking',
|
|
368
|
-
thinking: String(contentBlock.thinking || ''),
|
|
369
|
-
signature: String(contentBlock.signature || '')
|
|
370
|
-
});
|
|
371
|
-
} else {
|
|
372
|
-
contentBlocksByIndex.set(index, {
|
|
373
|
-
type: 'text',
|
|
374
|
-
text: String(contentBlock.text || '')
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
continue;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (eventType === 'content_block_delta') {
|
|
381
|
-
const index = Number(event?.index ?? 0);
|
|
382
|
-
const delta = event?.delta || {};
|
|
383
|
-
if (delta.type === 'text_delta' && delta.text) {
|
|
384
|
-
text += delta.text;
|
|
385
|
-
const current = contentBlocksByIndex.get(index) || { type: 'text', text: '' };
|
|
386
|
-
current.text = `${current.text || ''}${delta.text}`;
|
|
387
|
-
contentBlocksByIndex.set(index, current);
|
|
388
|
-
if (onTextDelta) onTextDelta(delta.text);
|
|
389
|
-
continue;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
if (delta.type === 'thinking_delta' && delta.thinking) {
|
|
393
|
-
const current = contentBlocksByIndex.get(index) || { type: 'thinking', thinking: '', signature: '' };
|
|
394
|
-
current.thinking = `${current.thinking || ''}${delta.thinking}`;
|
|
395
|
-
contentBlocksByIndex.set(index, current);
|
|
396
|
-
continue;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
if (delta.type === 'signature_delta') {
|
|
400
|
-
const current = contentBlocksByIndex.get(index) || { type: 'thinking', thinking: '', signature: '' };
|
|
401
|
-
current.signature = `${current.signature || ''}${String(delta.signature || '')}`;
|
|
402
|
-
contentBlocksByIndex.set(index, current);
|
|
403
|
-
continue;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
if (delta.type === 'input_json_delta') {
|
|
407
|
-
const current = toolCallsByIndex.get(index) || emptyToolCall(index);
|
|
408
|
-
current.arguments = `${current.arguments || ''}${String(delta.partial_json || '')}`;
|
|
409
|
-
toolCallsByIndex.set(index, current);
|
|
410
|
-
contentBlocksByIndex.set(index, {
|
|
411
|
-
type: 'tool_use',
|
|
412
|
-
id: current.id,
|
|
413
|
-
name: current.name,
|
|
414
|
-
arguments: current.arguments
|
|
415
|
-
});
|
|
416
|
-
if (onToolCallDelta) {
|
|
417
|
-
onToolCallDelta({
|
|
418
|
-
index,
|
|
419
|
-
id: current.id || `tc-${index + 1}`,
|
|
420
|
-
name: current.name,
|
|
421
|
-
arguments: current.arguments || '{}'
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
continue;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (eventType === 'message_delta') {
|
|
429
|
-
usage = mergeUsage(usage, event?.delta?.usage);
|
|
430
|
-
continue;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
if (eventType === 'message_stop') {
|
|
434
|
-
break;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
return buildFinalStreamResult(text, toolCallsByIndex, usage, messages, contentBlocksByIndex);
|
|
439
|
-
}
|