@wangzhizhi/remi 0.0.1-alpha
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/README.md +9 -0
- package/dist/doctor.js +108 -0
- package/dist/git.js +41 -0
- package/dist/help.js +27 -0
- package/dist/i18n.js +422 -0
- package/dist/index.js +97 -0
- package/dist/initPrompt.js +17 -0
- package/dist/model.js +116 -0
- package/dist/modelSelection.js +34 -0
- package/dist/permissionDisplay.js +46 -0
- package/dist/permissions.js +206 -0
- package/dist/repl.js +346 -0
- package/dist/resume.js +3 -0
- package/dist/setup.js +62 -0
- package/dist/statusline.js +59 -0
- package/dist/style.js +48 -0
- package/dist/syntaxTheme.js +39 -0
- package/dist/tui/RemiApp.js +1756 -0
- package/dist/tui/commands.js +427 -0
- package/dist/tui/index.js +42 -0
- package/dist/tui/renderers/Header.js +28 -0
- package/dist/tui/renderers/MessageList.js +1176 -0
- package/dist/tui/renderers/PromptBox.js +118 -0
- package/dist/tui/renderers/StatusLine.js +124 -0
- package/dist/tui/renderers/WorkingIndicator.js +70 -0
- package/dist/tui/slashCommandHighlight.js +8 -0
- package/dist/tui/theme.js +13 -0
- package/dist/tui/types.js +1 -0
- package/dist/usage.js +66 -0
- package/dist/version.js +5 -0
- package/node_modules/@remi/compact/dist/index.js +389 -0
- package/node_modules/@remi/compact/package.json +8 -0
- package/node_modules/@remi/config/dist/index.js +426 -0
- package/node_modules/@remi/config/package.json +8 -0
- package/node_modules/@remi/core/dist/contextBuilder.js +344 -0
- package/node_modules/@remi/core/dist/directoryOverview.js +359 -0
- package/node_modules/@remi/core/dist/index.js +2843 -0
- package/node_modules/@remi/core/dist/projectInstructions.js +123 -0
- package/node_modules/@remi/core/dist/responseStyles.js +98 -0
- package/node_modules/@remi/core/package.json +8 -0
- package/node_modules/@remi/llm/dist/index.js +804 -0
- package/node_modules/@remi/llm/package.json +8 -0
- package/node_modules/@remi/memory/dist/index.js +312 -0
- package/node_modules/@remi/memory/package.json +8 -0
- package/node_modules/@remi/permissions/dist/index.js +90 -0
- package/node_modules/@remi/permissions/package.json +8 -0
- package/node_modules/@remi/sessions/dist/index.js +370 -0
- package/node_modules/@remi/sessions/package.json +8 -0
- package/node_modules/@remi/skills/dist/index.js +273 -0
- package/node_modules/@remi/skills/package.json +8 -0
- package/node_modules/@remi/terminal-markdown/dist/index.js +1412 -0
- package/node_modules/@remi/terminal-markdown/package.json +8 -0
- package/node_modules/@remi/tools/dist/index.js +3875 -0
- package/node_modules/@remi/tools/package.json +8 -0
- package/package.json +48 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { createSessionStore, readSessionEvents, sessionSummaryPath } from '@remi/sessions';
|
|
4
|
+
export const compactPackageName = '@remi/compact';
|
|
5
|
+
const defaultMaxSummaryChars = 6_000;
|
|
6
|
+
const defaultMaxRecentItems = 6;
|
|
7
|
+
const defaultMaxToolItems = 8;
|
|
8
|
+
const defaultMaxSessionMemoryChars = 8_000;
|
|
9
|
+
const defaultSessionMemoryMinimumTokensToInit = 10_000;
|
|
10
|
+
const defaultSessionMemoryMinimumTokensBetweenUpdates = 5_000;
|
|
11
|
+
const defaultSessionMemoryToolCallsBetweenUpdates = 3;
|
|
12
|
+
export function estimateTextTokens(text) {
|
|
13
|
+
if (text.length === 0) {
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
let asciiLike = 0;
|
|
17
|
+
let wideLike = 0;
|
|
18
|
+
for (const char of text) {
|
|
19
|
+
const code = char.codePointAt(0) ?? 0;
|
|
20
|
+
if ((code >= 0x3400 && code <= 0x9fff) ||
|
|
21
|
+
(code >= 0xf900 && code <= 0xfaff) ||
|
|
22
|
+
(code >= 0x3040 && code <= 0x30ff) ||
|
|
23
|
+
(code >= 0xac00 && code <= 0xd7af)) {
|
|
24
|
+
wideLike += 1;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
asciiLike += 1;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return Math.max(1, Math.ceil(asciiLike / 4 + wideLike * 0.8));
|
|
31
|
+
}
|
|
32
|
+
export function estimateSessionTokens(events) {
|
|
33
|
+
return estimateTextTokens(events.map(event => eventText(event)).filter(Boolean).join('\n'));
|
|
34
|
+
}
|
|
35
|
+
export function latestCompactSummary(events) {
|
|
36
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
37
|
+
const event = events[index];
|
|
38
|
+
if (event?.type === 'compact' && event.summary.trim().length > 0) {
|
|
39
|
+
return event.summary;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
export function eventsAfterLatestCompact(events) {
|
|
45
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
46
|
+
if (events[index]?.type === 'compact') {
|
|
47
|
+
return events.slice(index + 1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return events;
|
|
51
|
+
}
|
|
52
|
+
export function readSessionMemory(cwd, sessionId) {
|
|
53
|
+
const path = sessionSummaryPath(cwd, sessionId);
|
|
54
|
+
if (!existsSync(path)) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
const parsed = parseSessionMemoryFile(readFileSync(path, 'utf8'), path);
|
|
58
|
+
if (!parsed || parsed.sessionId !== sessionId || parsed.content.trim().length === 0) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return parsed;
|
|
62
|
+
}
|
|
63
|
+
export function buildSessionMemorySummary(events, options = {}) {
|
|
64
|
+
const maxSummaryChars = options.maxSummaryChars ?? defaultMaxSessionMemoryChars;
|
|
65
|
+
const maxRecentItems = options.maxRecentItems ?? defaultMaxRecentItems;
|
|
66
|
+
const maxToolItems = options.maxToolItems ?? defaultMaxToolItems;
|
|
67
|
+
const now = options.now ?? new Date().toISOString();
|
|
68
|
+
const estimatedTokens = estimateSessionTokens(events);
|
|
69
|
+
const users = events.filter((event) => event.type === 'user');
|
|
70
|
+
const assistants = events.filter((event) => event.type === 'assistant');
|
|
71
|
+
const systems = events.filter((event) => event.type === 'system');
|
|
72
|
+
const toolCalls = events.filter((event) => event.type === 'tool_call');
|
|
73
|
+
const toolResults = events.filter((event) => event.type === 'tool_result');
|
|
74
|
+
const latestUser = last(users);
|
|
75
|
+
const latestAssistant = last(assistants);
|
|
76
|
+
const fileHints = uniqueRecent([
|
|
77
|
+
...toolCalls.flatMap(event => toolCallHints(event)),
|
|
78
|
+
...toolResults.flatMap(event => toolResultHints(event)),
|
|
79
|
+
], maxToolItems);
|
|
80
|
+
const failures = [
|
|
81
|
+
...systems.filter(event => event.level === 'error' || event.level === 'warn').map(event => `${event.level}: ${oneLine(event.content, 260)}`),
|
|
82
|
+
...toolResults.filter(event => !event.ok || event.errorCode).map(event => `${event.toolName}: ${event.errorCode ?? 'failed'} - ${oneLine(event.summary, 260)}`),
|
|
83
|
+
].slice(-maxToolItems);
|
|
84
|
+
const recentToolResults = toolResults.slice(-maxToolItems).map(event => `- ${formatToolResult(event)}`);
|
|
85
|
+
const sections = [
|
|
86
|
+
'# Remi Session Memory',
|
|
87
|
+
'',
|
|
88
|
+
`Updated: ${now}`,
|
|
89
|
+
`Source events: ${events.length}`,
|
|
90
|
+
`Estimated source tokens: ${estimatedTokens}`,
|
|
91
|
+
'',
|
|
92
|
+
'## Current State',
|
|
93
|
+
`- Latest user objective: ${latestUser ? oneLine(latestUser.content, 320) : 'No user objective recorded yet.'}`,
|
|
94
|
+
`- Latest assistant result: ${latestAssistant ? oneLine(latestAssistant.content, 320) : 'No assistant result recorded yet.'}`,
|
|
95
|
+
'',
|
|
96
|
+
...section('User Requests and Constraints', users.slice(-maxRecentItems).map(event => `- ${oneLine(event.content, 320)}`)),
|
|
97
|
+
...section('Files Changed or Inspected', fileHints.map(hint => `- ${hint}`)),
|
|
98
|
+
...section('Tool Results That Still Matter', recentToolResults),
|
|
99
|
+
...section('Errors and Corrections', failures.map(issue => `- ${issue}`)),
|
|
100
|
+
...section('Recent Decisions and Results', assistants.slice(-maxRecentItems).map(event => `- ${oneLine(event.content, 320)}`)),
|
|
101
|
+
'',
|
|
102
|
+
'## Pending Tasks and Next Step',
|
|
103
|
+
'- Continue from the latest user objective. Treat newer transcript messages after this summary as more authoritative.',
|
|
104
|
+
];
|
|
105
|
+
const summary = truncateText(sections.join('\n'), maxSummaryChars);
|
|
106
|
+
return {
|
|
107
|
+
summary,
|
|
108
|
+
estimatedTokens,
|
|
109
|
+
sourceEventCount: events.length,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
export function updateSessionMemory(cwd, sessionId, options = {}) {
|
|
113
|
+
const result = buildSessionMemorySummary(readSessionEvents(cwd, sessionId), options);
|
|
114
|
+
const now = options.now ?? new Date().toISOString();
|
|
115
|
+
const path = sessionSummaryPath(cwd, sessionId);
|
|
116
|
+
const summary = {
|
|
117
|
+
sessionId,
|
|
118
|
+
path,
|
|
119
|
+
content: result.summary,
|
|
120
|
+
updatedAt: now,
|
|
121
|
+
estimatedTokens: result.estimatedTokens,
|
|
122
|
+
sourceEventCount: result.sourceEventCount,
|
|
123
|
+
};
|
|
124
|
+
writeSessionMemoryFile(summary);
|
|
125
|
+
return summary;
|
|
126
|
+
}
|
|
127
|
+
export function maybeUpdateSessionMemory(cwd, sessionId, options = {}) {
|
|
128
|
+
const events = readSessionEvents(cwd, sessionId);
|
|
129
|
+
const previous = readSessionMemory(cwd, sessionId);
|
|
130
|
+
const decision = sessionMemoryUpdateReason(events, previous, options);
|
|
131
|
+
if (!decision.update) {
|
|
132
|
+
return previous ? { updated: false, reason: decision.reason, previous } : { updated: false, reason: decision.reason };
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
updated: true,
|
|
136
|
+
reason: decision.reason,
|
|
137
|
+
summary: updateSessionMemory(cwd, sessionId, options),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
export function buildCompactSummary(events, options = {}) {
|
|
141
|
+
const maxSummaryChars = options.maxSummaryChars ?? defaultMaxSummaryChars;
|
|
142
|
+
const maxRecentItems = options.maxRecentItems ?? defaultMaxRecentItems;
|
|
143
|
+
const maxToolItems = options.maxToolItems ?? defaultMaxToolItems;
|
|
144
|
+
const now = options.now ?? new Date().toISOString();
|
|
145
|
+
const priorSummary = latestCompactSummary(events);
|
|
146
|
+
const activeEvents = eventsAfterLatestCompact(events);
|
|
147
|
+
const sourceEvents = activeEvents.length > 0 ? activeEvents : events;
|
|
148
|
+
const estimatedTokens = estimateSessionTokens(sourceEvents);
|
|
149
|
+
const users = sourceEvents.filter((event) => event.type === 'user');
|
|
150
|
+
const assistants = sourceEvents.filter((event) => event.type === 'assistant');
|
|
151
|
+
const systems = sourceEvents.filter((event) => event.type === 'system');
|
|
152
|
+
const toolResults = sourceEvents.filter((event) => event.type === 'tool_result');
|
|
153
|
+
const latestUser = last(users);
|
|
154
|
+
const currentObjective = latestUser ? oneLine(latestUser.content, 240) : 'No current user objective found in this session segment.';
|
|
155
|
+
const openIssues = [
|
|
156
|
+
...systems.filter(event => event.level === 'error' || event.level === 'warn').map(event => `${event.level}: ${oneLine(event.content, 220)}`),
|
|
157
|
+
...toolResults.filter(event => !event.ok || event.errorCode).map(event => `${event.toolName}: ${event.errorCode ?? 'failed'} - ${oneLine(event.summary, 220)}`),
|
|
158
|
+
].slice(-maxToolItems);
|
|
159
|
+
const sections = [
|
|
160
|
+
'# Remi Compact Summary',
|
|
161
|
+
'',
|
|
162
|
+
`Created: ${now}`,
|
|
163
|
+
`Source events since previous compact: ${sourceEvents.length}`,
|
|
164
|
+
`Estimated source tokens: ${estimatedTokens}`,
|
|
165
|
+
'',
|
|
166
|
+
'## Current Objective',
|
|
167
|
+
`- ${currentObjective}`,
|
|
168
|
+
'',
|
|
169
|
+
...section('Prior Compact Summary', priorSummary ? [truncateText(priorSummary, 1_200)] : []),
|
|
170
|
+
...section('Session Memory', options.sessionMemorySummary ? [truncateText(options.sessionMemorySummary, 1_800)] : []),
|
|
171
|
+
...section('Recent User Requests', users.slice(-maxRecentItems).map(event => `- ${oneLine(event.content, 260)}`)),
|
|
172
|
+
...section('Recent Assistant Results', assistants.slice(-maxRecentItems).map(event => `- ${oneLine(event.content, 260)}`)),
|
|
173
|
+
...section('Tool Activity and Failures', toolResults.slice(-maxToolItems).map(event => `- ${formatToolResult(event)}`)),
|
|
174
|
+
...section('Open Issues', openIssues.map(issue => `- ${issue}`)),
|
|
175
|
+
'',
|
|
176
|
+
'## Resume Guidance',
|
|
177
|
+
'- Use this summary as continuity context. Prefer newer user messages after this boundary when they conflict with older details.',
|
|
178
|
+
];
|
|
179
|
+
const summary = truncateText(sections.join('\n'), maxSummaryChars);
|
|
180
|
+
return {
|
|
181
|
+
summary,
|
|
182
|
+
estimatedTokens,
|
|
183
|
+
sourceEventCount: sourceEvents.length,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
export function compactSession(cwd, sessionId, options = {}) {
|
|
187
|
+
const sessionMemory = options.sessionMemorySummary ?? freshSessionMemoryForCompact(cwd, sessionId, options);
|
|
188
|
+
const result = buildCompactSummary(readSessionEvents(cwd, sessionId), {
|
|
189
|
+
...options,
|
|
190
|
+
...(sessionMemory ? { sessionMemorySummary: sessionMemory } : {}),
|
|
191
|
+
});
|
|
192
|
+
const trigger = options.trigger ?? 'manual';
|
|
193
|
+
const strategy = options.strategy ?? 'deterministic';
|
|
194
|
+
createSessionStore(cwd, sessionId).append({
|
|
195
|
+
type: 'compact',
|
|
196
|
+
summary: result.summary,
|
|
197
|
+
estimatedTokens: result.estimatedTokens,
|
|
198
|
+
sourceEventCount: result.sourceEventCount,
|
|
199
|
+
trigger,
|
|
200
|
+
strategy,
|
|
201
|
+
});
|
|
202
|
+
return {
|
|
203
|
+
...result,
|
|
204
|
+
sessionId,
|
|
205
|
+
trigger,
|
|
206
|
+
strategy,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function freshSessionMemoryForCompact(cwd, sessionId, options) {
|
|
210
|
+
try {
|
|
211
|
+
return updateSessionMemory(cwd, sessionId, {
|
|
212
|
+
force: true,
|
|
213
|
+
...(options.now !== undefined ? { now: options.now } : {}),
|
|
214
|
+
maxSummaryChars: Math.min(options.maxSummaryChars ?? defaultMaxSessionMemoryChars, defaultMaxSessionMemoryChars),
|
|
215
|
+
...(options.maxRecentItems !== undefined ? { maxRecentItems: options.maxRecentItems } : {}),
|
|
216
|
+
...(options.maxToolItems !== undefined ? { maxToolItems: options.maxToolItems } : {}),
|
|
217
|
+
}).content;
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return readSessionMemory(cwd, sessionId)?.content;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function writeSessionMemoryFile(summary) {
|
|
224
|
+
mkdirSync(dirname(summary.path), { recursive: true });
|
|
225
|
+
const frontmatter = [
|
|
226
|
+
'---',
|
|
227
|
+
`sessionId: ${summary.sessionId}`,
|
|
228
|
+
`updatedAt: ${summary.updatedAt}`,
|
|
229
|
+
`sourceEventCount: ${summary.sourceEventCount}`,
|
|
230
|
+
`estimatedTokens: ${summary.estimatedTokens}`,
|
|
231
|
+
'---',
|
|
232
|
+
'',
|
|
233
|
+
].join('\n');
|
|
234
|
+
writeFileSync(summary.path, `${frontmatter}${summary.content.trim()}\n`, { mode: 0o600 });
|
|
235
|
+
}
|
|
236
|
+
function parseSessionMemoryFile(content, path) {
|
|
237
|
+
const lines = content.split(/\r?\n/);
|
|
238
|
+
if (lines[0]?.trim() !== '---') {
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
const endIndex = lines.findIndex((line, index) => index > 0 && line.trim() === '---');
|
|
242
|
+
if (endIndex <= 0) {
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
const fields = parseSimpleFrontmatter(lines.slice(1, endIndex));
|
|
246
|
+
const sessionId = fields.get('sessionId');
|
|
247
|
+
const updatedAt = fields.get('updatedAt');
|
|
248
|
+
const sourceEventCount = Number.parseInt(fields.get('sourceEventCount') ?? '', 10);
|
|
249
|
+
const estimatedTokens = Number.parseInt(fields.get('estimatedTokens') ?? '', 10);
|
|
250
|
+
const body = lines.slice(endIndex + 1).join('\n').trim();
|
|
251
|
+
if (!sessionId || !updatedAt || !Number.isFinite(sourceEventCount) || !Number.isFinite(estimatedTokens)) {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
sessionId,
|
|
256
|
+
path,
|
|
257
|
+
content: body,
|
|
258
|
+
updatedAt,
|
|
259
|
+
sourceEventCount,
|
|
260
|
+
estimatedTokens,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function parseSimpleFrontmatter(lines) {
|
|
264
|
+
const fields = new Map();
|
|
265
|
+
for (const line of lines) {
|
|
266
|
+
const separatorIndex = line.indexOf(':');
|
|
267
|
+
if (separatorIndex <= 0) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
fields.set(line.slice(0, separatorIndex).trim(), line.slice(separatorIndex + 1).trim());
|
|
271
|
+
}
|
|
272
|
+
return fields;
|
|
273
|
+
}
|
|
274
|
+
function sessionMemoryUpdateReason(events, previous, options) {
|
|
275
|
+
if (options.force) {
|
|
276
|
+
return { update: true, reason: 'forced' };
|
|
277
|
+
}
|
|
278
|
+
if (events.length === 0) {
|
|
279
|
+
return { update: false, reason: 'empty session' };
|
|
280
|
+
}
|
|
281
|
+
if (last(events)?.type === 'tool_call') {
|
|
282
|
+
return { update: false, reason: 'tool call still pending' };
|
|
283
|
+
}
|
|
284
|
+
const estimatedTokens = estimateSessionTokens(events);
|
|
285
|
+
const minimumTokensToInit = options.minimumTokensToInit ?? defaultSessionMemoryMinimumTokensToInit;
|
|
286
|
+
if (!previous) {
|
|
287
|
+
if (estimatedTokens >= minimumTokensToInit) {
|
|
288
|
+
return { update: true, reason: `initial threshold met (${estimatedTokens} tokens)` };
|
|
289
|
+
}
|
|
290
|
+
return { update: false, reason: `below initial threshold (${estimatedTokens}/${minimumTokensToInit} tokens)` };
|
|
291
|
+
}
|
|
292
|
+
if (events.length <= previous.sourceEventCount) {
|
|
293
|
+
return { update: false, reason: 'no new events since previous summary' };
|
|
294
|
+
}
|
|
295
|
+
const minimumTokensBetweenUpdates = options.minimumTokensBetweenUpdates ?? defaultSessionMemoryMinimumTokensBetweenUpdates;
|
|
296
|
+
const tokensSincePrevious = estimatedTokens - previous.estimatedTokens;
|
|
297
|
+
if (tokensSincePrevious < minimumTokensBetweenUpdates) {
|
|
298
|
+
return { update: false, reason: `below update threshold (${tokensSincePrevious}/${minimumTokensBetweenUpdates} tokens)` };
|
|
299
|
+
}
|
|
300
|
+
const newEvents = events.slice(previous.sourceEventCount);
|
|
301
|
+
const toolEventsSincePrevious = newEvents.filter(event => event.type === 'tool_call' || event.type === 'tool_result').length;
|
|
302
|
+
const toolCallsBetweenUpdates = options.toolCallsBetweenUpdates ?? defaultSessionMemoryToolCallsBetweenUpdates;
|
|
303
|
+
const naturalBreak = last(events)?.type === 'assistant' || last(events)?.type === 'user';
|
|
304
|
+
if (toolEventsSincePrevious >= toolCallsBetweenUpdates || naturalBreak) {
|
|
305
|
+
return { update: true, reason: `update threshold met (${tokensSincePrevious} tokens, ${toolEventsSincePrevious} tool events)` };
|
|
306
|
+
}
|
|
307
|
+
return { update: false, reason: `waiting for tool activity or natural break (${toolEventsSincePrevious}/${toolCallsBetweenUpdates} tool events)` };
|
|
308
|
+
}
|
|
309
|
+
function section(title, lines) {
|
|
310
|
+
if (lines.length === 0) {
|
|
311
|
+
return [];
|
|
312
|
+
}
|
|
313
|
+
return ['', `## ${title}`, ...lines];
|
|
314
|
+
}
|
|
315
|
+
function last(items) {
|
|
316
|
+
return items.length > 0 ? items[items.length - 1] : undefined;
|
|
317
|
+
}
|
|
318
|
+
function eventText(event) {
|
|
319
|
+
if (event.type === 'user' || event.type === 'system' || event.type === 'assistant') {
|
|
320
|
+
return event.content;
|
|
321
|
+
}
|
|
322
|
+
if (event.type === 'tool_call') {
|
|
323
|
+
return `${event.toolName} ${JSON.stringify(event.input)}`;
|
|
324
|
+
}
|
|
325
|
+
if (event.type === 'tool_result') {
|
|
326
|
+
return `${event.toolName} ${event.summary}${artifactSuffix(event)}`;
|
|
327
|
+
}
|
|
328
|
+
if (event.type === 'compact') {
|
|
329
|
+
return event.summary;
|
|
330
|
+
}
|
|
331
|
+
return '';
|
|
332
|
+
}
|
|
333
|
+
function formatToolResult(event) {
|
|
334
|
+
return `${event.toolName}: ${event.ok ? 'ok' : 'failed'} - ${oneLine(event.summary, 260)}${artifactSuffix(event)}`;
|
|
335
|
+
}
|
|
336
|
+
function toolCallHints(event) {
|
|
337
|
+
const input = event.input;
|
|
338
|
+
return [
|
|
339
|
+
stringField(input, 'path'),
|
|
340
|
+
stringField(input, 'filePath'),
|
|
341
|
+
stringField(input, 'targetPath'),
|
|
342
|
+
stringField(input, 'cwd'),
|
|
343
|
+
stringField(input, 'command'),
|
|
344
|
+
].filter((value) => Boolean(value)).map(value => `${event.toolName}: ${oneLine(value, 220)}`);
|
|
345
|
+
}
|
|
346
|
+
function toolResultHints(event) {
|
|
347
|
+
if (!/(read|list|search|glob|write|edit|delete|shell|command)/i.test(event.toolName)) {
|
|
348
|
+
return [];
|
|
349
|
+
}
|
|
350
|
+
return [`${event.toolName}: ${oneLine(event.summary, 240)}${artifactSuffix(event)}`];
|
|
351
|
+
}
|
|
352
|
+
function stringField(input, key) {
|
|
353
|
+
const value = input[key];
|
|
354
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
|
|
355
|
+
}
|
|
356
|
+
function uniqueRecent(values, limit) {
|
|
357
|
+
const selected = [];
|
|
358
|
+
const seen = new Set();
|
|
359
|
+
for (let index = values.length - 1; index >= 0; index -= 1) {
|
|
360
|
+
const value = values[index];
|
|
361
|
+
if (!value || seen.has(value)) {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
selected.unshift(value);
|
|
365
|
+
seen.add(value);
|
|
366
|
+
if (selected.length >= limit) {
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return selected;
|
|
371
|
+
}
|
|
372
|
+
function artifactSuffix(event) {
|
|
373
|
+
if (!event.artifact) {
|
|
374
|
+
return '';
|
|
375
|
+
}
|
|
376
|
+
return ` (artifact: ${event.artifact.path}, ${event.artifact.bytes} bytes)`;
|
|
377
|
+
}
|
|
378
|
+
function oneLine(text, maxLength) {
|
|
379
|
+
return truncateText(text.replace(/\s+/g, ' ').trim(), maxLength);
|
|
380
|
+
}
|
|
381
|
+
function truncateText(text, maxLength) {
|
|
382
|
+
if (text.length <= maxLength) {
|
|
383
|
+
return text;
|
|
384
|
+
}
|
|
385
|
+
if (maxLength <= 3) {
|
|
386
|
+
return text.slice(0, maxLength);
|
|
387
|
+
}
|
|
388
|
+
return `${text.slice(0, maxLength - 3)}...`;
|
|
389
|
+
}
|