codemini-cli 0.3.1 → 0.3.2
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/package.json +2 -1
- package/src/commands/chat.js +1 -0
- package/src/commands/run.js +6 -2
- package/src/core/agent-loop.js +1 -1
- package/src/core/chat-runtime.js +546 -136
- package/src/core/config-store.js +30 -0
- package/src/core/memory-policy.js +27 -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/tools.js +173 -0
- package/src/tui/chat-app.js +224 -24
|
@@ -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
|
+
}
|
package/src/core/tools.js
CHANGED
|
@@ -17,6 +17,7 @@ import { initializeProjectIndex, queryProjectIndex, refreshIndexedFile } from '.
|
|
|
17
17
|
import { checkReadDedup } from './agent-loop.js';
|
|
18
18
|
import { TOOL_SKIP_DIRS as SKIP_DIRS, TEXT_EXTENSIONS, CODE_WRITE_GUARD_EXTENSIONS, LANGUAGE_FILE_TYPES } from './constants.js';
|
|
19
19
|
import { sha256Prefixed as sha256, sha1 } from './crypto-utils.js';
|
|
20
|
+
import { forgetMemory, listMemories, rememberMemory, searchMemories } from './memory-store.js';
|
|
20
21
|
const SERVICE_RECENT_LOG_LIMIT = 80;
|
|
21
22
|
const SERVICE_STARTUP_POLL_MS = 150;
|
|
22
23
|
const serviceRegistry = new Map();
|
|
@@ -1960,6 +1961,101 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1960
1961
|
required: ['query']
|
|
1961
1962
|
}
|
|
1962
1963
|
}
|
|
1964
|
+
},
|
|
1965
|
+
{
|
|
1966
|
+
type: 'function',
|
|
1967
|
+
function: {
|
|
1968
|
+
name: 'remember_user',
|
|
1969
|
+
description: 'Store a durable user preference, communication habit, or long-term instruction for future sessions. Use this for things like reply style, language, explanation depth, or stable guardrails. Never store secrets, tokens, passwords, or one-off task details.',
|
|
1970
|
+
parameters: {
|
|
1971
|
+
type: 'object',
|
|
1972
|
+
properties: {
|
|
1973
|
+
content: { type: 'string', description: 'Stable preference or instruction to remember' },
|
|
1974
|
+
kind: { type: 'string', description: 'preference, workflow, constraint, or warning' },
|
|
1975
|
+
summary: { type: 'string', description: 'Short summary for the memory index' },
|
|
1976
|
+
replace_similar: { type: 'boolean', description: 'Replace an existing similar memory when true' }
|
|
1977
|
+
},
|
|
1978
|
+
required: ['content']
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
},
|
|
1982
|
+
{
|
|
1983
|
+
type: 'function',
|
|
1984
|
+
function: {
|
|
1985
|
+
name: 'remember_global',
|
|
1986
|
+
description: 'Store a durable cross-project workflow, environment fact, or generally reusable lesson that can help across many repositories. Use this for stable habits like preferred search tools or repeatable debugging workflow. Never store secrets.',
|
|
1987
|
+
parameters: {
|
|
1988
|
+
type: 'object',
|
|
1989
|
+
properties: {
|
|
1990
|
+
content: { type: 'string' },
|
|
1991
|
+
kind: { type: 'string' },
|
|
1992
|
+
summary: { type: 'string' },
|
|
1993
|
+
replace_similar: { type: 'boolean' }
|
|
1994
|
+
},
|
|
1995
|
+
required: ['content']
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
},
|
|
1999
|
+
{
|
|
2000
|
+
type: 'function',
|
|
2001
|
+
function: {
|
|
2002
|
+
name: 'remember_project',
|
|
2003
|
+
description: 'Store a durable project-specific convention, architecture note, key module warning, or local workflow expectation. Use this for repository-specific rules, important files, testing conventions, or architectural boundaries. Never store secrets or transient task state.',
|
|
2004
|
+
parameters: {
|
|
2005
|
+
type: 'object',
|
|
2006
|
+
properties: {
|
|
2007
|
+
content: { type: 'string' },
|
|
2008
|
+
kind: { type: 'string' },
|
|
2009
|
+
summary: { type: 'string' },
|
|
2010
|
+
replace_similar: { type: 'boolean' }
|
|
2011
|
+
},
|
|
2012
|
+
required: ['content']
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
},
|
|
2016
|
+
{
|
|
2017
|
+
type: 'function',
|
|
2018
|
+
function: {
|
|
2019
|
+
name: 'list_memory',
|
|
2020
|
+
description: 'List stored persistent memories for one scope.',
|
|
2021
|
+
parameters: {
|
|
2022
|
+
type: 'object',
|
|
2023
|
+
properties: {
|
|
2024
|
+
scope: { type: 'string', description: 'user, global, or project' }
|
|
2025
|
+
},
|
|
2026
|
+
required: ['scope']
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
},
|
|
2030
|
+
{
|
|
2031
|
+
type: 'function',
|
|
2032
|
+
function: {
|
|
2033
|
+
name: 'search_memory',
|
|
2034
|
+
description: 'Search stored persistent memories for one scope.',
|
|
2035
|
+
parameters: {
|
|
2036
|
+
type: 'object',
|
|
2037
|
+
properties: {
|
|
2038
|
+
scope: { type: 'string', description: 'user, global, or project' },
|
|
2039
|
+
query: { type: 'string', description: 'Search phrase' }
|
|
2040
|
+
},
|
|
2041
|
+
required: ['scope', 'query']
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
},
|
|
2045
|
+
{
|
|
2046
|
+
type: 'function',
|
|
2047
|
+
function: {
|
|
2048
|
+
name: 'forget_memory',
|
|
2049
|
+
description: 'Delete a stored persistent memory by id.',
|
|
2050
|
+
parameters: {
|
|
2051
|
+
type: 'object',
|
|
2052
|
+
properties: {
|
|
2053
|
+
scope: { type: 'string', description: 'user, global, or project' },
|
|
2054
|
+
id: { type: 'string', description: 'Memory id to delete' }
|
|
2055
|
+
},
|
|
2056
|
+
required: ['scope', 'id']
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
1963
2059
|
}
|
|
1964
2060
|
];
|
|
1965
2061
|
|
|
@@ -2180,6 +2276,55 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2180
2276
|
return result;
|
|
2181
2277
|
},
|
|
2182
2278
|
run: (args) => runCommand(workspaceRoot, config, args),
|
|
2279
|
+
remember_user: async (args = {}) => {
|
|
2280
|
+
const saved = await rememberMemory({
|
|
2281
|
+
scope: 'user',
|
|
2282
|
+
content: args.content,
|
|
2283
|
+
kind: args.kind,
|
|
2284
|
+
summary: args.summary,
|
|
2285
|
+
replaceSimilar: args.replace_similar !== false,
|
|
2286
|
+
workspaceRoot,
|
|
2287
|
+
config
|
|
2288
|
+
});
|
|
2289
|
+
return { ok: true, scope: 'user', memory: saved };
|
|
2290
|
+
},
|
|
2291
|
+
remember_global: async (args = {}) => {
|
|
2292
|
+
const saved = await rememberMemory({
|
|
2293
|
+
scope: 'global',
|
|
2294
|
+
content: args.content,
|
|
2295
|
+
kind: args.kind,
|
|
2296
|
+
summary: args.summary,
|
|
2297
|
+
replaceSimilar: args.replace_similar !== false,
|
|
2298
|
+
workspaceRoot,
|
|
2299
|
+
config
|
|
2300
|
+
});
|
|
2301
|
+
return { ok: true, scope: 'global', memory: saved };
|
|
2302
|
+
},
|
|
2303
|
+
remember_project: async (args = {}) => {
|
|
2304
|
+
const saved = await rememberMemory({
|
|
2305
|
+
scope: 'project',
|
|
2306
|
+
content: args.content,
|
|
2307
|
+
kind: args.kind,
|
|
2308
|
+
summary: args.summary,
|
|
2309
|
+
replaceSimilar: args.replace_similar !== false,
|
|
2310
|
+
workspaceRoot,
|
|
2311
|
+
config
|
|
2312
|
+
});
|
|
2313
|
+
return { ok: true, scope: 'project', memory: saved };
|
|
2314
|
+
},
|
|
2315
|
+
list_memory: async (args = {}) => ({
|
|
2316
|
+
scope: String(args.scope || ''),
|
|
2317
|
+
items: await listMemories({ scope: args.scope, workspaceRoot })
|
|
2318
|
+
}),
|
|
2319
|
+
search_memory: async (args = {}) => ({
|
|
2320
|
+
scope: String(args.scope || ''),
|
|
2321
|
+
query: String(args.query || ''),
|
|
2322
|
+
items: await searchMemories({ scope: args.scope, query: args.query, workspaceRoot })
|
|
2323
|
+
}),
|
|
2324
|
+
forget_memory: async (args = {}) => ({
|
|
2325
|
+
ok: true,
|
|
2326
|
+
...(await forgetMemory({ scope: args.scope, id: args.id, workspaceRoot }))
|
|
2327
|
+
}),
|
|
2183
2328
|
start_service: (args) => startService(workspaceRoot, config, args),
|
|
2184
2329
|
list_services: () => listServices(workspaceRoot),
|
|
2185
2330
|
get_service_status: (args) => getServiceStatus(workspaceRoot, args),
|
|
@@ -2332,6 +2477,34 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2332
2477
|
return parts.join('\n');
|
|
2333
2478
|
},
|
|
2334
2479
|
|
|
2480
|
+
remember_user(result) {
|
|
2481
|
+
return result?.memory?.content ? `stored user memory: ${result.memory.content}` : JSON.stringify(result);
|
|
2482
|
+
},
|
|
2483
|
+
|
|
2484
|
+
remember_global(result) {
|
|
2485
|
+
return result?.memory?.content ? `stored global memory: ${result.memory.content}` : JSON.stringify(result);
|
|
2486
|
+
},
|
|
2487
|
+
|
|
2488
|
+
remember_project(result) {
|
|
2489
|
+
return result?.memory?.content ? `stored project memory: ${result.memory.content}` : JSON.stringify(result);
|
|
2490
|
+
},
|
|
2491
|
+
|
|
2492
|
+
list_memory(result) {
|
|
2493
|
+
if (!result || typeof result !== 'object' || !Array.isArray(result.items)) return JSON.stringify(result);
|
|
2494
|
+
if (result.items.length === 0) return `No ${result.scope || ''} memories found.`;
|
|
2495
|
+
return result.items.map((item) => `${item.id} [${item.kind}] ${item.content}`).join('\n');
|
|
2496
|
+
},
|
|
2497
|
+
|
|
2498
|
+
search_memory(result) {
|
|
2499
|
+
if (!result || typeof result !== 'object' || !Array.isArray(result.items)) return JSON.stringify(result);
|
|
2500
|
+
if (result.items.length === 0) return `No ${result.scope || ''} memories matched "${result.query || ''}".`;
|
|
2501
|
+
return result.items.map((item) => `${item.id} [${item.kind}] ${item.content}`).join('\n');
|
|
2502
|
+
},
|
|
2503
|
+
|
|
2504
|
+
forget_memory(result) {
|
|
2505
|
+
return `removed ${Number(result?.removed || 0)} memory item(s)`;
|
|
2506
|
+
},
|
|
2507
|
+
|
|
2335
2508
|
generate_diff(result) {
|
|
2336
2509
|
if (!result || typeof result !== 'object') return String(result);
|
|
2337
2510
|
const p = result.path || '';
|