codemini-cli 0.3.1 → 0.3.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 -0
- package/README.md +199 -133
- package/package.json +2 -1
- package/src/commands/chat.js +1 -0
- package/src/commands/run.js +6 -2
- package/src/core/agent-loop.js +20 -19
- package/src/core/chat-runtime.js +567 -233
- package/src/core/checkpoint-store.js +2 -3
- package/src/core/command-policy.js +144 -10
- package/src/core/config-store.js +36 -10
- package/src/core/context-compact.js +7 -1
- package/src/core/default-system-prompt.js +12 -1
- package/src/core/memory-policy.js +33 -0
- package/src/core/memory-prompt.js +45 -0
- package/src/core/memory-store.js +181 -0
- package/src/core/paths.js +8 -0
- package/src/core/provider/anthropic.js +388 -0
- package/src/core/provider/index.js +37 -0
- package/src/core/session-store.js +4 -0
- package/src/core/shell-profile.js +29 -17
- package/src/core/todo-state.js +19 -0
- package/src/core/tools.js +486 -235
- package/src/tui/chat-app.js +278 -57
- package/src/tui/tool-activity/presenters/command.js +8 -15
- package/src/tui/tool-activity/presenters/misc.js +2 -5
- package/src/core/task-store.js +0 -117
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
function extractTextContent(content) {
|
|
2
|
+
if (typeof content === 'string') return content;
|
|
3
|
+
if (Array.isArray(content)) {
|
|
4
|
+
return content
|
|
5
|
+
.map((part) => {
|
|
6
|
+
if (typeof part === 'string') return part;
|
|
7
|
+
if (part?.type === 'text') return part.text || '';
|
|
8
|
+
return '';
|
|
9
|
+
})
|
|
10
|
+
.join('');
|
|
11
|
+
}
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeIncomingToolCallArguments(argumentsValue) {
|
|
16
|
+
if (typeof argumentsValue === 'string') return argumentsValue;
|
|
17
|
+
if (argumentsValue == null) return '{}';
|
|
18
|
+
try {
|
|
19
|
+
return JSON.stringify(argumentsValue);
|
|
20
|
+
} catch {
|
|
21
|
+
return '{}';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function tryParseJsonObject(raw) {
|
|
26
|
+
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
27
|
+
return raw;
|
|
28
|
+
}
|
|
29
|
+
if (typeof raw !== 'string') return {};
|
|
30
|
+
try {
|
|
31
|
+
const parsed = JSON.parse(raw);
|
|
32
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
33
|
+
return parsed;
|
|
34
|
+
}
|
|
35
|
+
} catch {}
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function normalizeMessages(messages) {
|
|
40
|
+
const source = Array.isArray(messages) ? messages : [];
|
|
41
|
+
const systemParts = [];
|
|
42
|
+
const out = [];
|
|
43
|
+
|
|
44
|
+
for (const message of source) {
|
|
45
|
+
if (!message || typeof message !== 'object') continue;
|
|
46
|
+
if (message.role === 'system') {
|
|
47
|
+
const text = extractTextContent(message.content);
|
|
48
|
+
if (text) systemParts.push(text);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (message.role === 'tool') {
|
|
53
|
+
out.push({
|
|
54
|
+
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
|
+
]
|
|
62
|
+
});
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const contentBlocks = [];
|
|
67
|
+
const text = extractTextContent(message.content);
|
|
68
|
+
if (text) {
|
|
69
|
+
contentBlocks.push({ type: 'text', text });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
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
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
out.push({
|
|
86
|
+
role: message.role,
|
|
87
|
+
content: contentBlocks
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
system: systemParts.join('\n\n').trim() || undefined,
|
|
93
|
+
messages: out
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function normalizeTools(tools) {
|
|
98
|
+
const source = Array.isArray(tools) ? tools : [];
|
|
99
|
+
return source
|
|
100
|
+
.map((tool) => {
|
|
101
|
+
const fn = tool?.function || {};
|
|
102
|
+
const name = String(fn.name || '').trim();
|
|
103
|
+
if (!name) return null;
|
|
104
|
+
return {
|
|
105
|
+
name,
|
|
106
|
+
...(fn.description ? { description: String(fn.description) } : {}),
|
|
107
|
+
input_schema: fn.parameters && typeof fn.parameters === 'object' ? fn.parameters : { type: 'object' }
|
|
108
|
+
};
|
|
109
|
+
})
|
|
110
|
+
.filter(Boolean);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function buildPayload({ model, temperature, messages, tools, stream = false, maxTokens = 4096 }) {
|
|
114
|
+
const normalized = normalizeMessages(messages);
|
|
115
|
+
const payload = {
|
|
116
|
+
model,
|
|
117
|
+
max_tokens: maxTokens,
|
|
118
|
+
temperature,
|
|
119
|
+
messages: normalized.messages
|
|
120
|
+
};
|
|
121
|
+
if (normalized.system) payload.system = normalized.system;
|
|
122
|
+
if (stream) payload.stream = true;
|
|
123
|
+
|
|
124
|
+
const normalizedTools = normalizeTools(tools);
|
|
125
|
+
if (normalizedTools.length > 0) {
|
|
126
|
+
payload.tools = normalizedTools;
|
|
127
|
+
payload.tool_choice = { type: 'auto' };
|
|
128
|
+
}
|
|
129
|
+
return payload;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function hasTrailingToolContext(messages) {
|
|
133
|
+
const source = Array.isArray(messages) ? messages : [];
|
|
134
|
+
for (let index = source.length - 1; index >= 0; index -= 1) {
|
|
135
|
+
const message = source[index];
|
|
136
|
+
if (!message || typeof message !== 'object') continue;
|
|
137
|
+
if (message.role === 'tool') return true;
|
|
138
|
+
if (message.role === 'assistant' || message.role === 'user') return false;
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function extractAssistantResult(data, messages) {
|
|
144
|
+
const content = Array.isArray(data?.content) ? data.content : [];
|
|
145
|
+
const text = content
|
|
146
|
+
.filter((block) => block?.type === 'text')
|
|
147
|
+
.map((block) => block.text || '')
|
|
148
|
+
.join('');
|
|
149
|
+
const toolCalls = content
|
|
150
|
+
.filter((block) => block?.type === 'tool_use')
|
|
151
|
+
.map((block) => ({
|
|
152
|
+
id: String(block.id || ''),
|
|
153
|
+
name: String(block.name || ''),
|
|
154
|
+
arguments: normalizeIncomingToolCallArguments(block.input)
|
|
155
|
+
}))
|
|
156
|
+
.filter((toolCall) => toolCall.name);
|
|
157
|
+
const normalizedText = String(text || '').trim();
|
|
158
|
+
|
|
159
|
+
if (!normalizedText && toolCalls.length === 0) {
|
|
160
|
+
if (hasTrailingToolContext(messages)) {
|
|
161
|
+
return {
|
|
162
|
+
text: '',
|
|
163
|
+
toolCalls: [],
|
|
164
|
+
usage: data?.usage || null,
|
|
165
|
+
incomplete: true,
|
|
166
|
+
content
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
throw new Error('Anthropic gateway returned empty assistant response');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
text,
|
|
174
|
+
toolCalls,
|
|
175
|
+
usage: data?.usage || null,
|
|
176
|
+
content
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function createHeaders(apiKey) {
|
|
181
|
+
return {
|
|
182
|
+
'content-type': 'application/json',
|
|
183
|
+
'x-api-key': apiKey,
|
|
184
|
+
'anthropic-version': '2023-06-01'
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function buildMessagesUrl(baseUrl) {
|
|
189
|
+
return `${String(baseUrl || '').replace(/\/$/, '')}/v1/messages`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function parseJsonResponse(response) {
|
|
193
|
+
if (!response.ok) {
|
|
194
|
+
const text = await response.text().catch(() => '');
|
|
195
|
+
throw new Error(`Anthropic gateway error ${response.status}: ${text || response.statusText}`);
|
|
196
|
+
}
|
|
197
|
+
return response.json();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function mergeUsage(current, next) {
|
|
201
|
+
if (!next || typeof next !== 'object') return current;
|
|
202
|
+
return {
|
|
203
|
+
...(current || {}),
|
|
204
|
+
...next
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function emptyToolCall(index) {
|
|
209
|
+
return {
|
|
210
|
+
index,
|
|
211
|
+
id: '',
|
|
212
|
+
name: '',
|
|
213
|
+
arguments: ''
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function buildFinalStreamResult(text, toolCallsByIndex, usage, messages) {
|
|
218
|
+
const toolCalls = Array.from(toolCallsByIndex.entries())
|
|
219
|
+
.sort((a, b) => a[0] - b[0])
|
|
220
|
+
.map(([, tc], i) => ({
|
|
221
|
+
id: tc.id || `tc-${i + 1}`,
|
|
222
|
+
name: tc.name,
|
|
223
|
+
arguments: tc.arguments || '{}'
|
|
224
|
+
}))
|
|
225
|
+
.filter((tc) => tc.name);
|
|
226
|
+
const normalizedText = String(text || '').trim();
|
|
227
|
+
const content = [];
|
|
228
|
+
if (text) content.push({ type: 'text', text });
|
|
229
|
+
for (const toolCall of toolCalls) {
|
|
230
|
+
content.push({
|
|
231
|
+
type: 'tool_use',
|
|
232
|
+
id: toolCall.id,
|
|
233
|
+
name: toolCall.name,
|
|
234
|
+
input: tryParseJsonObject(toolCall.arguments)
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!normalizedText && toolCalls.length === 0) {
|
|
239
|
+
if (hasTrailingToolContext(messages)) {
|
|
240
|
+
return {
|
|
241
|
+
text: '',
|
|
242
|
+
toolCalls: [],
|
|
243
|
+
usage,
|
|
244
|
+
incomplete: true,
|
|
245
|
+
content: []
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
throw new Error('Anthropic gateway stream returned empty assistant response');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
text,
|
|
253
|
+
toolCalls,
|
|
254
|
+
usage,
|
|
255
|
+
incomplete: false,
|
|
256
|
+
content
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function* iterateSseEvents(stream) {
|
|
261
|
+
const decoder = new TextDecoder();
|
|
262
|
+
let buffer = '';
|
|
263
|
+
|
|
264
|
+
for await (const chunk of stream) {
|
|
265
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
266
|
+
while (buffer.includes('\n\n')) {
|
|
267
|
+
const boundary = buffer.indexOf('\n\n');
|
|
268
|
+
const rawEvent = buffer.slice(0, boundary);
|
|
269
|
+
buffer = buffer.slice(boundary + 2);
|
|
270
|
+
const lines = rawEvent.split('\n');
|
|
271
|
+
let event = 'message';
|
|
272
|
+
const dataLines = [];
|
|
273
|
+
for (const line of lines) {
|
|
274
|
+
if (line.startsWith('event:')) {
|
|
275
|
+
event = line.slice(6).trim();
|
|
276
|
+
} else if (line.startsWith('data:')) {
|
|
277
|
+
dataLines.push(line.slice(5).trimStart());
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const dataText = dataLines.join('\n');
|
|
281
|
+
if (!dataText || dataText === '[DONE]') continue;
|
|
282
|
+
yield {
|
|
283
|
+
event,
|
|
284
|
+
data: JSON.parse(dataText)
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export async function createChatCompletion({
|
|
291
|
+
baseUrl,
|
|
292
|
+
apiKey,
|
|
293
|
+
model,
|
|
294
|
+
messages,
|
|
295
|
+
temperature = 0.2,
|
|
296
|
+
tools,
|
|
297
|
+
timeoutMs = 90000,
|
|
298
|
+
maxTokens = 4096
|
|
299
|
+
}) {
|
|
300
|
+
const payload = buildPayload({ model, temperature, messages, tools, maxTokens });
|
|
301
|
+
const response = await fetch(buildMessagesUrl(baseUrl), {
|
|
302
|
+
method: 'POST',
|
|
303
|
+
headers: createHeaders(apiKey),
|
|
304
|
+
body: JSON.stringify(payload),
|
|
305
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
306
|
+
});
|
|
307
|
+
const data = await parseJsonResponse(response);
|
|
308
|
+
return extractAssistantResult(data, messages);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export async function createChatCompletionStream({
|
|
312
|
+
baseUrl,
|
|
313
|
+
apiKey,
|
|
314
|
+
model,
|
|
315
|
+
messages,
|
|
316
|
+
temperature = 0.2,
|
|
317
|
+
tools,
|
|
318
|
+
onTextDelta,
|
|
319
|
+
onToolCallDelta,
|
|
320
|
+
timeoutMs = 90000,
|
|
321
|
+
maxTokens = 4096
|
|
322
|
+
}) {
|
|
323
|
+
const payload = buildPayload({ model, temperature, messages, tools, stream: true, maxTokens });
|
|
324
|
+
const response = await fetch(buildMessagesUrl(baseUrl), {
|
|
325
|
+
method: 'POST',
|
|
326
|
+
headers: createHeaders(apiKey),
|
|
327
|
+
body: JSON.stringify(payload),
|
|
328
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
if (!response.ok || !response.body) {
|
|
332
|
+
const text = await response.text().catch(() => '');
|
|
333
|
+
throw new Error(`Anthropic gateway error ${response.status}: ${text || response.statusText}`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
let text = '';
|
|
337
|
+
let usage = null;
|
|
338
|
+
const toolCallsByIndex = new Map();
|
|
339
|
+
|
|
340
|
+
for await (const chunk of iterateSseEvents(response.body)) {
|
|
341
|
+
usage = mergeUsage(usage, chunk?.data?.usage);
|
|
342
|
+
usage = mergeUsage(usage, chunk?.data?.message?.usage);
|
|
343
|
+
|
|
344
|
+
if (chunk.event === 'content_block_start') {
|
|
345
|
+
const index = Number(chunk?.data?.index ?? 0);
|
|
346
|
+
const contentBlock = chunk?.data?.content_block || {};
|
|
347
|
+
if (contentBlock.type === 'tool_use') {
|
|
348
|
+
const current = toolCallsByIndex.get(index) || emptyToolCall(index);
|
|
349
|
+
current.id = String(contentBlock.id || current.id || '');
|
|
350
|
+
current.name = String(contentBlock.name || current.name || '');
|
|
351
|
+
const initialInput = contentBlock.input && Object.keys(contentBlock.input).length > 0
|
|
352
|
+
? normalizeIncomingToolCallArguments(contentBlock.input)
|
|
353
|
+
: '';
|
|
354
|
+
current.arguments = current.arguments || initialInput;
|
|
355
|
+
toolCallsByIndex.set(index, current);
|
|
356
|
+
}
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (chunk.event !== 'content_block_delta') {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const index = Number(chunk?.data?.index ?? 0);
|
|
365
|
+
const delta = chunk?.data?.delta || {};
|
|
366
|
+
if (delta.type === 'text_delta' && delta.text) {
|
|
367
|
+
text += delta.text;
|
|
368
|
+
if (onTextDelta) onTextDelta(delta.text);
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (delta.type === 'input_json_delta') {
|
|
373
|
+
const current = toolCallsByIndex.get(index) || emptyToolCall(index);
|
|
374
|
+
current.arguments = `${current.arguments || ''}${String(delta.partial_json || '')}`;
|
|
375
|
+
toolCallsByIndex.set(index, current);
|
|
376
|
+
if (onToolCallDelta) {
|
|
377
|
+
onToolCallDelta({
|
|
378
|
+
index,
|
|
379
|
+
id: current.id || `tc-${index + 1}`,
|
|
380
|
+
name: current.name,
|
|
381
|
+
arguments: current.arguments || '{}'
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return buildFinalStreamResult(text, toolCallsByIndex, usage, messages);
|
|
388
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createChatCompletion as createOpenAICompatibleChatCompletion,
|
|
3
|
+
createChatCompletionStream as createOpenAICompatibleChatCompletionStream
|
|
4
|
+
} from './openai-compatible.js';
|
|
5
|
+
import {
|
|
6
|
+
createChatCompletion as createAnthropicChatCompletion,
|
|
7
|
+
createChatCompletionStream as createAnthropicChatCompletionStream
|
|
8
|
+
} from './anthropic.js';
|
|
9
|
+
|
|
10
|
+
function normalizeSdkProvider(value) {
|
|
11
|
+
const raw = String(value || '').trim().toLowerCase();
|
|
12
|
+
if (raw === 'anthropic') return 'anthropic';
|
|
13
|
+
return 'openai-compatible';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getSdkProvider(configOrValue) {
|
|
17
|
+
if (configOrValue && typeof configOrValue === 'object' && !Array.isArray(configOrValue)) {
|
|
18
|
+
return normalizeSdkProvider(configOrValue?.sdk?.provider);
|
|
19
|
+
}
|
|
20
|
+
return normalizeSdkProvider(configOrValue);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function createChatCompletion(options) {
|
|
24
|
+
const provider = getSdkProvider(options?.sdkProvider);
|
|
25
|
+
if (provider === 'anthropic') {
|
|
26
|
+
return createAnthropicChatCompletion(options);
|
|
27
|
+
}
|
|
28
|
+
return createOpenAICompatibleChatCompletion(options);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function createChatCompletionStream(options) {
|
|
32
|
+
const provider = getSdkProvider(options?.sdkProvider);
|
|
33
|
+
if (provider === 'anthropic') {
|
|
34
|
+
return createAnthropicChatCompletionStream(options);
|
|
35
|
+
}
|
|
36
|
+
return createOpenAICompatibleChatCompletionStream(options);
|
|
37
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { getSessionsDir } from './paths.js';
|
|
4
|
+
import { normalizeTodos } from './todo-state.js';
|
|
4
5
|
|
|
5
6
|
const ALLOWED_ROLES = new Set(['system', 'user', 'assistant', 'tool']);
|
|
6
7
|
|
|
@@ -86,6 +87,9 @@ function sanitizeSession(session, fallbackId = '') {
|
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
const todos = normalizeTodos(session?.todos);
|
|
91
|
+
if (todos.length > 0) out.todos = todos;
|
|
92
|
+
|
|
89
93
|
return out;
|
|
90
94
|
}
|
|
91
95
|
|
|
@@ -123,30 +123,46 @@ export function getShellSystemPrompt(value) {
|
|
|
123
123
|
# Using your tools
|
|
124
124
|
|
|
125
125
|
ALWAYS prefer dedicated tools over raw shell commands:
|
|
126
|
+
- The visible default tool list is intentionally small. If a needed capability is not currently listed, do not assume it is unavailable — call tool_search to load additional tools first
|
|
126
127
|
- Use query_project_index first for broad repository understanding. It combines project-map metadata with indexed file symbols so you can narrow candidates before reading source files
|
|
127
128
|
- Use read to inspect files — NEVER use cat, head, or tail via run. read returns content directly by default; demo-style shapes like {file_path:"src/app.ts"}, {path:"src/app.ts:10-40"}, or {file_path:"src/app.ts", offset:10, limit:30} are accepted
|
|
128
129
|
- Use grep to search file contents — NEVER use grep or rg via run
|
|
129
|
-
- Use
|
|
130
|
+
- Use list for directory-by-directory filesystem discovery. If you specifically need pattern-based file lookup like src/**/*.ts, load glob with tool_search instead of falling back to run
|
|
130
131
|
- Use edit to modify existing files — this is the DEFAULT path for code changes. Demo-style aliases like {file_path:"src/app.ts", old_string:"foo", new_string:"bar"} are accepted
|
|
131
132
|
- Use write only for creating new files or complete rewrites (set full_file_rewrite=true for existing code files). Aliases like {file:"notes.txt", text:"..."} are accepted
|
|
132
|
-
- Use
|
|
133
|
-
- Use run for
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
- Use update_todos to manage the session todo checklist for complex work. Provide the full current list each time and usually keep exactly one item in_progress
|
|
134
|
+
- Use run for shell commands. For long-running processes (dev servers, watchers), set run_in_background=true when you know you do not need the final result immediately. Long-running commands may also be backgrounded automatically
|
|
135
|
+
|
|
136
|
+
Use update_todos with these rules:
|
|
137
|
+
- MUST use it before major tool work when the task has 3 or more meaningful steps, multiple files or phases, explicit verification work, debugging with multiple hypotheses, or any non-trivial implementation likely to span several tool calls
|
|
138
|
+
- Do NOT use it for single-step trivial edits, one-off command execution, or purely informational/chat responses
|
|
139
|
+
- The input must be the full current checklist, not a partial patch
|
|
140
|
+
- Keep exactly one item in_progress while work is actively underway unless the user explicitly asks for parallel execution
|
|
141
|
+
- Mark items completed immediately after finishing them, and add newly discovered follow-up work as new checklist items
|
|
142
|
+
- If tests fail, verification is incomplete, or a blocker remains, do not mark the affected item completed
|
|
143
|
+
- Before giving a completion-style final answer for a complex task, update_todos so the checklist is either fully completed or clearly shows the remaining blocker
|
|
144
|
+
|
|
145
|
+
Some tools are loaded on demand through tool_search. Common examples:
|
|
146
|
+
- glob for pattern-based file lookup
|
|
147
|
+
- ast_query and read_ast_node for AST-scoped edits
|
|
148
|
+
- generate_diff and patch for explicit diff workflows
|
|
149
|
+
- list_background_tasks, get_background_task, and stop_background_task for managing long-running background commands
|
|
150
|
+
- remember_user, remember_global, remember_project, list_memory, search_memory, and forget_memory for persistent memory operations
|
|
151
|
+
|
|
152
|
+
For structural code edits (functions, classes, methods), load the AST tools and use the AST-first workflow:
|
|
153
|
+
tool_search("ast_query") → ast_query → read_ast_node → edit with ast_target and kind=replace_block.
|
|
138
154
|
Fall back to plain grep/read/edit only when AST is not appropriate.
|
|
139
155
|
|
|
140
|
-
For
|
|
141
|
-
|
|
142
|
-
Some tools are loaded on demand. If a needed tool is not listed, call tool_search first to load it.
|
|
156
|
+
For background commands: use run to launch. If you need management tools that are not currently visible, load list_background_tasks/get_background_task/stop_background_task with tool_search. Prefer reading the returned output_file with read instead of asking for a separate logs tool.
|
|
143
157
|
|
|
144
158
|
Common tool call patterns:
|
|
145
159
|
- Query the project index first: {query:"login auth flow", path:"src", max_results:5}
|
|
160
|
+
- Load a deferred tool when needed: {query:"glob"} or {query:"all"}
|
|
146
161
|
- Read a file: {path:"src/app.ts"} or {file_path:"src/app.ts", offset:20, limit:40}
|
|
147
162
|
- Read a specific range inline: {path:"src/app.ts:20-60"}
|
|
148
163
|
- Search text: {pattern:"loginUser", path:"src"} or {query:"loginUser", directory:"src"}
|
|
149
|
-
-
|
|
164
|
+
- List a directory first: {path:"src"}
|
|
165
|
+
- After loading glob, find files by pattern: {pattern:"src/**/*.ts"} or {query:"src/**/*.ts"}
|
|
150
166
|
- Edit exact text: {file_path:"src/app.ts", old_string:"foo", new_string:"bar"}
|
|
151
167
|
- Edit with shorthand: {path:"src/app.ts", old_text:"foo", content:"bar"}
|
|
152
168
|
- Write a new file: {file:"notes.txt", text:"..."} or {path:"src/page.tsx", content:"..."}
|
|
@@ -159,17 +175,13 @@ Common tool call patterns:
|
|
|
159
175
|
- The user shares your workspace with you; prefer inspecting the project yourself before asking them to paste files that should be discoverable
|
|
160
176
|
- Before substantial tool work, send a short progress update to the user about what you are about to inspect or do
|
|
161
177
|
- Do not jump straight into tools without a brief user-facing note when the task is actionable
|
|
162
|
-
-
|
|
163
|
-
- For
|
|
164
|
-
- Do not read files one by one after a wide glob when query_project_index can narrow the candidates first
|
|
178
|
+
- For tasks with 3 or more meaningful steps, proactively create and maintain a todo checklist with update_todos
|
|
179
|
+
- For complex tasks, create the todo checklist before the first major implementation or verification tool call
|
|
165
180
|
- If a command or tool is blocked or fails, inspect the error and retry with allowed commands or tools
|
|
166
181
|
- For AST-scoped edits, if edit rejects due to missing or stale ast_target, fix arguments and retry
|
|
167
182
|
- Do not claim filesystem access is impossible unless search/read tools also fail
|
|
168
|
-
- Prefer editing existing files over creating new ones
|
|
169
183
|
- Do not add comments, docstrings, or type annotations to code you did not change
|
|
170
184
|
- Do not add features or refactor code beyond what was asked
|
|
171
|
-
- When a tool result is large, keep only the useful summary in your reply and read the saved output only if it is needed
|
|
172
|
-
- Keep tool results compact in context: prefer short conclusions over re-pasting raw output
|
|
173
185
|
|
|
174
186
|
# Plan mode
|
|
175
187
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function normalizeTodoStatus(value) {
|
|
2
|
+
const status = String(value || 'pending').trim().toLowerCase();
|
|
3
|
+
return ['pending', 'in_progress', 'completed'].includes(status) ? status : 'pending';
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function normalizeTodos(value) {
|
|
7
|
+
if (!Array.isArray(value)) return [];
|
|
8
|
+
return value
|
|
9
|
+
.map((item) => ({
|
|
10
|
+
content: String(item?.content || '').trim(),
|
|
11
|
+
activeForm: String(item?.activeForm || '').trim(),
|
|
12
|
+
status: normalizeTodoStatus(item?.status)
|
|
13
|
+
}))
|
|
14
|
+
.filter((item) => item.content && item.activeForm);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function countActiveTodos(todos) {
|
|
18
|
+
return normalizeTodos(todos).filter((item) => item.status !== 'completed').length;
|
|
19
|
+
}
|