agentel 0.2.0
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/LICENSE +21 -0
- package/README.md +452 -0
- package/agentlog-spec.md +551 -0
- package/bin/agentlog-recall.js +8 -0
- package/bin/agentlog.js +14 -0
- package/docs/code-reference.md +1108 -0
- package/docs/history-source-handling.md +837 -0
- package/docs/release.md +69 -0
- package/package.json +57 -0
- package/src/archive.js +1130 -0
- package/src/autostart.js +182 -0
- package/src/canonical-events.js +575 -0
- package/src/cli.js +7928 -0
- package/src/collector.js +113 -0
- package/src/commands/logs.js +51 -0
- package/src/commands/server.js +11 -0
- package/src/config.js +240 -0
- package/src/doctor.js +102 -0
- package/src/importers/aider.js +553 -0
- package/src/importers/claude.js +349 -0
- package/src/importers/cline.js +471 -0
- package/src/importers/gemini.js +795 -0
- package/src/importers/providers.js +149 -0
- package/src/importers/shared.js +15 -0
- package/src/importers.js +7063 -0
- package/src/mcp.js +148 -0
- package/src/parser-versions.js +62 -0
- package/src/paths.js +61 -0
- package/src/redaction.js +228 -0
- package/src/repo.js +106 -0
- package/src/search.js +619 -0
- package/src/sources.js +86 -0
- package/src/supervisor.js +217 -0
- package/src/sync.js +677 -0
- package/src/version.js +7 -0
- package/src/web-accounts.js +122 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const crypto = require("crypto");
|
|
4
|
+
const { parserVersionForSource } = require("./parser-versions");
|
|
5
|
+
|
|
6
|
+
const CANONICAL_EVENT_SCHEMA_VERSION = "agentlog.events.v1";
|
|
7
|
+
|
|
8
|
+
const EVENT_KINDS = {
|
|
9
|
+
SESSION_STARTED: "session.started",
|
|
10
|
+
PROMPT_SUBMITTED: "prompt.submitted",
|
|
11
|
+
TOOL_CALLED: "tool.called",
|
|
12
|
+
TOOL_COMPLETED: "tool.completed",
|
|
13
|
+
RESPONSE_GENERATED: "response.generated"
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const PATH_ARGUMENT_KEYS = new Set([
|
|
17
|
+
"workdir",
|
|
18
|
+
"path",
|
|
19
|
+
"paths",
|
|
20
|
+
"cwd",
|
|
21
|
+
"file",
|
|
22
|
+
"files",
|
|
23
|
+
"filename",
|
|
24
|
+
"filenames",
|
|
25
|
+
"dir",
|
|
26
|
+
"dirs",
|
|
27
|
+
"directory",
|
|
28
|
+
"directories",
|
|
29
|
+
"root",
|
|
30
|
+
"roots",
|
|
31
|
+
"repo",
|
|
32
|
+
"repo_path",
|
|
33
|
+
"repo_root",
|
|
34
|
+
"workspace",
|
|
35
|
+
"workspace_path",
|
|
36
|
+
"absolute_path",
|
|
37
|
+
"relative_path",
|
|
38
|
+
"target_path",
|
|
39
|
+
"source_path",
|
|
40
|
+
"file_path"
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const TOOL_CATEGORY_DEFINITIONS = [
|
|
44
|
+
{
|
|
45
|
+
category: "shell",
|
|
46
|
+
label: "Shell",
|
|
47
|
+
icon: ">",
|
|
48
|
+
names: ["bash", "bashoutput", "killbash", "shell", "exec", "exec_command", "command", "execute", "terminal"]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
category: "edit",
|
|
52
|
+
label: "Edit",
|
|
53
|
+
icon: "E",
|
|
54
|
+
names: ["edit", "multiedit", "write", "notebookedit", "apply_patch", "patch", "replace", "update_file"]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
category: "read",
|
|
58
|
+
label: "Read",
|
|
59
|
+
icon: "R",
|
|
60
|
+
names: ["read", "notebookread", "ls", "list", "view", "view_image", "open", "cat", "sed"]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
category: "search",
|
|
64
|
+
label: "Search",
|
|
65
|
+
icon: "?",
|
|
66
|
+
names: ["grep", "glob", "find", "rg", "search", "websearch"]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
category: "web",
|
|
70
|
+
label: "Web",
|
|
71
|
+
icon: "W",
|
|
72
|
+
names: ["webfetch", "fetch", "browser", "open_url", "navigate"]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
category: "task",
|
|
76
|
+
label: "Task",
|
|
77
|
+
icon: "A",
|
|
78
|
+
names: ["task", "todowrite", "todo_write", "spawn_agent", "wait_agent", "send_input"]
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
category: "skill",
|
|
82
|
+
label: "Skill",
|
|
83
|
+
icon: "$",
|
|
84
|
+
names: ["skill", "load_skill", "read_skill"]
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
category: "mcp",
|
|
88
|
+
label: "MCP",
|
|
89
|
+
icon: "M",
|
|
90
|
+
names: ["mcp", "mcp_tool_call"]
|
|
91
|
+
}
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
function normalizeSessionEvents(session, messages, options = {}) {
|
|
95
|
+
const parserVersion = options.parserVersion ?? session.parserVersion ?? parserVersionForSource(session.sourceType);
|
|
96
|
+
const events = [
|
|
97
|
+
baseEvent(session, {
|
|
98
|
+
messageIndex: -1,
|
|
99
|
+
ordinal: 0,
|
|
100
|
+
kind: EVENT_KINDS.SESSION_STARTED,
|
|
101
|
+
occurredAt: session.startedAt,
|
|
102
|
+
parserVersion,
|
|
103
|
+
role: "system",
|
|
104
|
+
indexed: {
|
|
105
|
+
title: session.title || `${session.provider || "Agent"} session`,
|
|
106
|
+
summary: "Session started",
|
|
107
|
+
status: "started"
|
|
108
|
+
},
|
|
109
|
+
body: {
|
|
110
|
+
text: session.title || "",
|
|
111
|
+
toolCall: null,
|
|
112
|
+
toolResult: null
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
];
|
|
116
|
+
for (let index = 0; index < messages.length; index++) {
|
|
117
|
+
events.push(...messageToCanonicalEvents(messages[index], session, { ...options, parserVersion, messageIndex: index }));
|
|
118
|
+
}
|
|
119
|
+
return events;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function messageToCanonicalEvents(message, session, options = {}) {
|
|
123
|
+
const role = String(message?.role || "").toLowerCase();
|
|
124
|
+
if (!message || role === "system" || role === "developer") return [];
|
|
125
|
+
const messageIndex = Number.isFinite(Number(message.index)) ? Number(message.index) : options.messageIndex || 0;
|
|
126
|
+
const parserVersion = options.parserVersion ?? session.parserVersion ?? parserVersionForSource(session.sourceType);
|
|
127
|
+
const occurredAt = message.timestamp || session.startedAt || new Date().toISOString();
|
|
128
|
+
const content = String(message.content || "").trim();
|
|
129
|
+
const metadata = message.metadata || {};
|
|
130
|
+
const events = [];
|
|
131
|
+
|
|
132
|
+
if (role === "user") {
|
|
133
|
+
events.push(
|
|
134
|
+
baseEvent(session, {
|
|
135
|
+
messageIndex,
|
|
136
|
+
ordinal: 0,
|
|
137
|
+
kind: EVENT_KINDS.PROMPT_SUBMITTED,
|
|
138
|
+
occurredAt,
|
|
139
|
+
parserVersion,
|
|
140
|
+
role,
|
|
141
|
+
indexed: {
|
|
142
|
+
title: "User prompt",
|
|
143
|
+
summary: summarize(content),
|
|
144
|
+
status: "submitted"
|
|
145
|
+
},
|
|
146
|
+
body: { text: content, toolCall: null, toolResult: null }
|
|
147
|
+
})
|
|
148
|
+
);
|
|
149
|
+
return events;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let responseEventId = "";
|
|
153
|
+
if (role === "assistant" && content) {
|
|
154
|
+
const response = baseEvent(session, {
|
|
155
|
+
messageIndex,
|
|
156
|
+
ordinal: 0,
|
|
157
|
+
kind: EVENT_KINDS.RESPONSE_GENERATED,
|
|
158
|
+
occurredAt,
|
|
159
|
+
parserVersion,
|
|
160
|
+
role,
|
|
161
|
+
indexed: {
|
|
162
|
+
title: "Assistant response",
|
|
163
|
+
summary: summarize(content),
|
|
164
|
+
status: "generated"
|
|
165
|
+
},
|
|
166
|
+
body: { text: content, toolCall: null, toolResult: null }
|
|
167
|
+
});
|
|
168
|
+
responseEventId = response.eventId;
|
|
169
|
+
events.push(response);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const toolCalls = Array.isArray(metadata.toolCalls) ? metadata.toolCalls : [];
|
|
173
|
+
for (let index = 0; index < toolCalls.length; index++) {
|
|
174
|
+
const toolCall = normalizeToolCall(toolCalls[index], metadata.provider || session.provider);
|
|
175
|
+
if (!toolCall) continue;
|
|
176
|
+
const rendered = renderToolCallText(toolCall);
|
|
177
|
+
events.push(
|
|
178
|
+
baseEvent(session, {
|
|
179
|
+
messageIndex,
|
|
180
|
+
ordinal: index + 1,
|
|
181
|
+
kind: EVENT_KINDS.TOOL_CALLED,
|
|
182
|
+
occurredAt,
|
|
183
|
+
parserVersion,
|
|
184
|
+
role: "assistant",
|
|
185
|
+
parentEventId: responseEventId,
|
|
186
|
+
indexed: {
|
|
187
|
+
title: toolCall.title || toolCall.displayName || titleCaseToolName(toolCall.name || "tool"),
|
|
188
|
+
summary: summarize(rendered || toolCall.rawInputSummary || toolCall.argument || ""),
|
|
189
|
+
toolName: toolCall.name || toolCall.displayName || "",
|
|
190
|
+
toolCategory: toolCall.category || "",
|
|
191
|
+
toolIcon: toolCall.icon || "",
|
|
192
|
+
target: toolCall.target || "",
|
|
193
|
+
status: toolCall.status || "tool_call"
|
|
194
|
+
},
|
|
195
|
+
body: { text: rendered, toolCall, toolResult: null }
|
|
196
|
+
})
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const toolResult = metadata.toolResult ? normalizeToolResult(metadata.toolResult, metadata.provider || session.provider) : role === "tool" ? normalizeToolResult(content, metadata.provider || session.provider) : null;
|
|
201
|
+
if (toolResult) {
|
|
202
|
+
events.push(
|
|
203
|
+
baseEvent(session, {
|
|
204
|
+
messageIndex,
|
|
205
|
+
ordinal: events.length + 1,
|
|
206
|
+
kind: EVENT_KINDS.TOOL_COMPLETED,
|
|
207
|
+
occurredAt,
|
|
208
|
+
parserVersion,
|
|
209
|
+
role: "tool",
|
|
210
|
+
indexed: {
|
|
211
|
+
title: toolResult.title || toolResult.kind || "Tool result",
|
|
212
|
+
summary: summarize(toolResult.summary || toolResult.output || content),
|
|
213
|
+
toolName: toolResult.kind || "",
|
|
214
|
+
toolCategory: toolResult.category || "",
|
|
215
|
+
toolIcon: toolResult.icon || "",
|
|
216
|
+
status: toolResult.status || "completed"
|
|
217
|
+
},
|
|
218
|
+
body: { text: toolResult.output || content, toolCall: null, toolResult }
|
|
219
|
+
})
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return events;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function baseEvent(session, options) {
|
|
227
|
+
const body = options.body || {};
|
|
228
|
+
const indexed = {
|
|
229
|
+
title: String(options.indexed?.title || ""),
|
|
230
|
+
summary: String(options.indexed?.summary || "")
|
|
231
|
+
};
|
|
232
|
+
for (const key of ["toolName", "toolCategory", "toolIcon", "model", "status", "latencyMs", "target"]) {
|
|
233
|
+
if (options.indexed?.[key] !== undefined && options.indexed?.[key] !== "") indexed[key] = options.indexed[key];
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
schemaVersion: CANONICAL_EVENT_SCHEMA_VERSION,
|
|
237
|
+
eventId: stableEventId(
|
|
238
|
+
session.sessionId,
|
|
239
|
+
options.messageIndex,
|
|
240
|
+
options.kind,
|
|
241
|
+
options.ordinal || 0,
|
|
242
|
+
`${indexed.title}\n${indexed.summary}\n${body.text || ""}`
|
|
243
|
+
),
|
|
244
|
+
sessionId: session.sessionId,
|
|
245
|
+
messageIndex: options.messageIndex,
|
|
246
|
+
occurredAt: toIso(options.occurredAt) || new Date().toISOString(),
|
|
247
|
+
kind: options.kind,
|
|
248
|
+
provider: session.provider || "unknown",
|
|
249
|
+
sourceType: session.sourceType || "import",
|
|
250
|
+
parserVersion: options.parserVersion ?? null,
|
|
251
|
+
role: options.role || "",
|
|
252
|
+
repoCanonical: session.repoCanonical || "",
|
|
253
|
+
scopeCanonical: session.scopeCanonical || "",
|
|
254
|
+
indexed,
|
|
255
|
+
body: {
|
|
256
|
+
text: String(body.text || ""),
|
|
257
|
+
toolCall: body.toolCall || null,
|
|
258
|
+
toolResult: body.toolResult || null
|
|
259
|
+
},
|
|
260
|
+
parentEventId: options.parentEventId || ""
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function stableEventId(sessionId, messageIndex, kind, ordinal, content) {
|
|
265
|
+
const hash = crypto
|
|
266
|
+
.createHash("sha256")
|
|
267
|
+
.update(String(sessionId || ""))
|
|
268
|
+
.update("\0")
|
|
269
|
+
.update(String(messageIndex))
|
|
270
|
+
.update("\0")
|
|
271
|
+
.update(String(kind || ""))
|
|
272
|
+
.update("\0")
|
|
273
|
+
.update(String(ordinal || 0))
|
|
274
|
+
.update("\0")
|
|
275
|
+
.update(String(content || ""))
|
|
276
|
+
.digest("hex");
|
|
277
|
+
return `evt_${hash.slice(0, 24)}`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function renderEventText(event) {
|
|
281
|
+
const kind = event?.kind || "";
|
|
282
|
+
if (kind === EVENT_KINDS.SESSION_STARTED) return "";
|
|
283
|
+
if (kind === EVENT_KINDS.TOOL_CALLED) {
|
|
284
|
+
const toolCall = event.body?.toolCall || event.toolCall || {};
|
|
285
|
+
if (isLowSignalToolCall(toolCall)) return "";
|
|
286
|
+
return renderToolCallText(toolCall);
|
|
287
|
+
}
|
|
288
|
+
if (kind === EVENT_KINDS.TOOL_COMPLETED) {
|
|
289
|
+
const result = event.body?.toolResult || {};
|
|
290
|
+
const text = [event.indexed?.title, result.summary, result.output || event.body?.text].filter(Boolean).join("\n");
|
|
291
|
+
return approxTokenCount(text) > 700 ? [event.indexed?.title, result.summary].filter(Boolean).join(" ") : text;
|
|
292
|
+
}
|
|
293
|
+
return event.body?.text || event.indexed?.summary || "";
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function renderToolCallText(toolCall) {
|
|
297
|
+
const normalized = normalizeToolCall(toolCall, toolCall?.provider) || {};
|
|
298
|
+
const name = titleCaseToolName(normalized.displayName || normalized.name || "tool");
|
|
299
|
+
const clauses = toolArgumentClauses(normalized);
|
|
300
|
+
if (!clauses.length) return name;
|
|
301
|
+
return `${name} with ${joinClauses(clauses)}`.slice(0, 4000);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function isLowSignalToolCall(toolCall) {
|
|
305
|
+
const normalized = normalizeToolCall(toolCall, toolCall?.provider);
|
|
306
|
+
if (!normalized) return true;
|
|
307
|
+
const clauses = toolArgumentClauses(normalized);
|
|
308
|
+
if (!clauses.length) return true;
|
|
309
|
+
const renderedArgs = toolMeaningfulArgumentValues(normalized).join(" ");
|
|
310
|
+
if (approxTokenCount(renderedArgs) > 1000) return true;
|
|
311
|
+
return !/[a-z]/i.test(renderedArgs);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function classifyTool(name, rawCategory = "") {
|
|
315
|
+
const keys = [normalizeToolKey(name), normalizeToolKey(rawCategory)].filter(Boolean);
|
|
316
|
+
if (keys.some((key) => key.startsWith("mcp_") || key.includes("_mcp_") || key.includes("mcp_tool"))) {
|
|
317
|
+
return toolCategoryDefinition("mcp");
|
|
318
|
+
}
|
|
319
|
+
for (const definition of TOOL_CATEGORY_DEFINITIONS) {
|
|
320
|
+
if (definition.names.some((candidate) => keys.includes(candidate))) return definition;
|
|
321
|
+
}
|
|
322
|
+
if (keys.some((key) => key.includes("search") || key.includes("grep") || key.includes("glob"))) return toolCategoryDefinition("search");
|
|
323
|
+
if (keys.some((key) => key.includes("web") || key.includes("browser") || key.includes("url"))) return toolCategoryDefinition("web");
|
|
324
|
+
if (keys.some((key) => key.includes("read") || key.includes("list"))) return toolCategoryDefinition("read");
|
|
325
|
+
if (keys.some((key) => key.includes("edit") || key.includes("write") || key.includes("patch"))) return toolCategoryDefinition("edit");
|
|
326
|
+
if (keys.some((key) => key.includes("bash") || key.includes("shell") || key.includes("command"))) return toolCategoryDefinition("shell");
|
|
327
|
+
if (keys.some((key) => key.includes("task") || key.includes("todo") || key.includes("agent"))) return toolCategoryDefinition("task");
|
|
328
|
+
return { category: "tool", label: "Tool", icon: "T" };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function toolCategoryDefinition(category) {
|
|
332
|
+
return TOOL_CATEGORY_DEFINITIONS.find((definition) => definition.category === category) || { category: "tool", label: "Tool", icon: "T" };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function normalizeToolKey(value) {
|
|
336
|
+
return String(value || "")
|
|
337
|
+
.trim()
|
|
338
|
+
.toLowerCase()
|
|
339
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
340
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
341
|
+
.replace(/^_+|_+$/g, "");
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function normalizeToolCall(raw, provider = "") {
|
|
345
|
+
if (!raw) return null;
|
|
346
|
+
if (typeof raw === "string") {
|
|
347
|
+
const classification = classifyTool(raw);
|
|
348
|
+
return {
|
|
349
|
+
provider,
|
|
350
|
+
name: raw,
|
|
351
|
+
displayName: titleCaseToolName(raw),
|
|
352
|
+
category: classification.category,
|
|
353
|
+
categoryLabel: classification.label,
|
|
354
|
+
icon: classification.icon,
|
|
355
|
+
title: titleCaseToolName(raw),
|
|
356
|
+
argument: "",
|
|
357
|
+
rawInputSummary: "",
|
|
358
|
+
inputPreview: "",
|
|
359
|
+
target: ""
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
if (typeof raw !== "object") return null;
|
|
363
|
+
const parsedArguments = parseMaybeJson(raw.arguments ?? raw.args ?? raw.input ?? raw.rawInput ?? raw.argument);
|
|
364
|
+
const name = firstString(raw.name, raw.toolName, raw.displayName, raw.function?.name, raw.kind, raw.type, "tool");
|
|
365
|
+
const displayName = firstString(raw.displayName, titleCaseToolName(name));
|
|
366
|
+
const rawCategory = firstString(raw.rawCategory, raw.category, raw.kind, raw.type);
|
|
367
|
+
const classification = classifyTool(name, rawCategory);
|
|
368
|
+
const argument = firstString(raw.argument, raw.rawInputSummary, typeof parsedArguments === "string" ? parsedArguments : "");
|
|
369
|
+
const rawInputSummary = firstString(raw.rawInputSummary, argument);
|
|
370
|
+
const inputPreview = firstString(raw.inputPreview, rawInputSummary, summarizeToolInput(parsedArguments));
|
|
371
|
+
const target = firstString(raw.target, targetFromArguments(parsedArguments));
|
|
372
|
+
return {
|
|
373
|
+
provider: firstString(raw.provider, provider),
|
|
374
|
+
id: firstString(raw.id, raw.callId, raw.tool_call_id) || undefined,
|
|
375
|
+
name,
|
|
376
|
+
displayName,
|
|
377
|
+
rawCategory: rawCategory || undefined,
|
|
378
|
+
category: firstString(raw.normalizedCategory, raw.toolCategory, classification.category),
|
|
379
|
+
categoryLabel: firstString(raw.categoryLabel, classification.label),
|
|
380
|
+
icon: firstString(raw.icon, classification.icon),
|
|
381
|
+
title: firstString(raw.title, displayName),
|
|
382
|
+
status: firstString(raw.status) || undefined,
|
|
383
|
+
argument,
|
|
384
|
+
rawInputSummary,
|
|
385
|
+
inputPreview,
|
|
386
|
+
target,
|
|
387
|
+
arguments: parsedArguments && typeof parsedArguments === "object" && !Array.isArray(parsedArguments) ? parsedArguments : undefined
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function summarizeToolInput(value) {
|
|
392
|
+
if (value == null || value === "") return "";
|
|
393
|
+
if (typeof value === "string") return value.slice(0, 240);
|
|
394
|
+
if (typeof value !== "object") return String(value);
|
|
395
|
+
const args = Array.isArray(value) ? value : Object.fromEntries(Object.entries(value).filter(([key]) => !isPathArgumentKey(key)));
|
|
396
|
+
const rendered = Array.isArray(args)
|
|
397
|
+
? args.map(renderArgumentValue).filter(Boolean).join(", ")
|
|
398
|
+
: Object.entries(args)
|
|
399
|
+
.map(([key, item]) => {
|
|
400
|
+
const renderedValue = renderArgumentValue(item);
|
|
401
|
+
return renderedValue ? `${humanizeArgumentKey(key)} ${renderedValue}` : "";
|
|
402
|
+
})
|
|
403
|
+
.filter(Boolean)
|
|
404
|
+
.join(", ");
|
|
405
|
+
return rendered.slice(0, 240);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function targetFromArguments(value) {
|
|
409
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return "";
|
|
410
|
+
for (const [key, item] of Object.entries(value)) {
|
|
411
|
+
if (!isPathArgumentKey(key)) continue;
|
|
412
|
+
const rendered = renderArgumentValue(item);
|
|
413
|
+
if (rendered) return rendered.slice(0, 300);
|
|
414
|
+
}
|
|
415
|
+
return "";
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function normalizeToolResult(raw, provider = "") {
|
|
419
|
+
if (!raw) return null;
|
|
420
|
+
if (typeof raw === "string") {
|
|
421
|
+
const output = raw.trim();
|
|
422
|
+
if (!output) return null;
|
|
423
|
+
const classification = classifyTool("tool_output");
|
|
424
|
+
return {
|
|
425
|
+
provider,
|
|
426
|
+
kind: "Tool output",
|
|
427
|
+
title: "Tool result",
|
|
428
|
+
category: classification.category,
|
|
429
|
+
categoryLabel: classification.label,
|
|
430
|
+
icon: classification.icon,
|
|
431
|
+
summary: firstLine(output),
|
|
432
|
+
output,
|
|
433
|
+
lineCount: output.split("\n").length,
|
|
434
|
+
collapsed: output.split("\n").length > 18,
|
|
435
|
+
status: "completed"
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
if (typeof raw !== "object") return null;
|
|
439
|
+
const output = firstString(raw.output, raw.text, raw.content, raw.result);
|
|
440
|
+
const summary = firstString(raw.summary, raw.detail, firstLine(output));
|
|
441
|
+
const kind = firstString(raw.kind, raw.type, raw.toolName, "Tool output");
|
|
442
|
+
const rawCategory = firstString(raw.rawCategory, raw.category, raw.kind, raw.type);
|
|
443
|
+
const classification = classifyTool(firstString(raw.toolName, raw.name, kind), rawCategory);
|
|
444
|
+
return {
|
|
445
|
+
provider: firstString(raw.provider, provider),
|
|
446
|
+
kind,
|
|
447
|
+
title: firstString(raw.title, raw.kind, "Tool result"),
|
|
448
|
+
rawCategory: rawCategory || undefined,
|
|
449
|
+
category: firstString(raw.normalizedCategory, raw.toolCategory, classification.category),
|
|
450
|
+
categoryLabel: firstString(raw.categoryLabel, classification.label),
|
|
451
|
+
icon: firstString(raw.icon, classification.icon),
|
|
452
|
+
summary,
|
|
453
|
+
output,
|
|
454
|
+
lineCount: Number(raw.lineCount || (output ? output.split("\n").length : 0)) || 0,
|
|
455
|
+
collapsed: Boolean(raw.collapsed),
|
|
456
|
+
status: firstString(raw.status, "completed")
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function toolArgumentClauses(toolCall) {
|
|
461
|
+
const clauses = [];
|
|
462
|
+
const args = toolCall.arguments && typeof toolCall.arguments === "object" ? toolCall.arguments : {};
|
|
463
|
+
for (const [key, value] of Object.entries(args)) {
|
|
464
|
+
if (isPathArgumentKey(key)) continue;
|
|
465
|
+
const rendered = renderArgumentValue(value);
|
|
466
|
+
if (rendered) clauses.push(`${humanizeArgumentKey(key)} ${rendered}`);
|
|
467
|
+
}
|
|
468
|
+
const argument = String(toolCall.rawInputSummary || toolCall.argument || "").trim();
|
|
469
|
+
if (argument && !looksPathLike(argument) && !clauses.some((clause) => clause.includes(argument))) clauses.push(argument);
|
|
470
|
+
return clauses.map((clause) => clause.replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function toolMeaningfulArgumentValues(toolCall) {
|
|
474
|
+
const values = [];
|
|
475
|
+
const args = toolCall.arguments && typeof toolCall.arguments === "object" ? toolCall.arguments : {};
|
|
476
|
+
for (const [key, value] of Object.entries(args)) {
|
|
477
|
+
if (isPathArgumentKey(key)) continue;
|
|
478
|
+
const rendered = renderArgumentValue(value);
|
|
479
|
+
if (rendered) values.push(rendered);
|
|
480
|
+
}
|
|
481
|
+
const argument = String(toolCall.rawInputSummary || toolCall.argument || "").trim();
|
|
482
|
+
if (argument && !looksPathLike(argument)) values.push(argument);
|
|
483
|
+
return values;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function isPathArgumentKey(key) {
|
|
487
|
+
const normalized = String(key || "").trim().toLowerCase();
|
|
488
|
+
return PATH_ARGUMENT_KEYS.has(normalized) || /(^|_)(path|paths|file|files|dir|dirs|directory|directories|cwd|workspace|repo|root)$/.test(normalized);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function humanizeArgumentKey(key) {
|
|
492
|
+
const value = String(key || "argument").replace(/[_-]+/g, " ").trim();
|
|
493
|
+
return value.endsWith("s") ? value : value;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function renderArgumentValue(value) {
|
|
497
|
+
if (value == null) return "";
|
|
498
|
+
if (typeof value === "string") return value.slice(0, 300);
|
|
499
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
500
|
+
if (Array.isArray(value)) return value.map(renderArgumentValue).filter(Boolean).slice(0, 5).join(", ");
|
|
501
|
+
if (typeof value === "object") return JSON.stringify(value).slice(0, 300);
|
|
502
|
+
return "";
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function joinClauses(clauses) {
|
|
506
|
+
if (clauses.length <= 1) return clauses[0] || "";
|
|
507
|
+
if (clauses.length === 2) return `${clauses[0]} and ${clauses[1]}`;
|
|
508
|
+
return `${clauses.slice(0, -1).join(", ")}, and ${clauses[clauses.length - 1]}`;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function titleCaseToolName(value) {
|
|
512
|
+
const text = String(value || "tool").trim();
|
|
513
|
+
if (!text) return "Tool";
|
|
514
|
+
return text
|
|
515
|
+
.split(/_+/g)
|
|
516
|
+
.map((part) => part.replace(/(^|[-\s])([a-z])/g, (_, prefix, char) => `${prefix}${char.toUpperCase()}`))
|
|
517
|
+
.join(" ");
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function summarize(value, max = 300) {
|
|
521
|
+
const text = String(value || "").replace(/\s+/g, " ").trim();
|
|
522
|
+
if (text.length <= max) return text;
|
|
523
|
+
return `${text.slice(0, max - 1).trim()}…`;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function parseMaybeJson(value) {
|
|
527
|
+
if (typeof value !== "string") return value;
|
|
528
|
+
const text = value.trim();
|
|
529
|
+
if (!text) return "";
|
|
530
|
+
if (!/^[\[{"]/.test(text)) return text;
|
|
531
|
+
try {
|
|
532
|
+
return JSON.parse(text);
|
|
533
|
+
} catch {
|
|
534
|
+
return text;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function firstString(...values) {
|
|
539
|
+
for (const value of values) {
|
|
540
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
541
|
+
}
|
|
542
|
+
return "";
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function firstLine(value) {
|
|
546
|
+
return String(value || "").split("\n").find((line) => line.trim())?.trim() || "";
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function approxTokenCount(value) {
|
|
550
|
+
return String(value || "").split(/\s+/).filter(Boolean).length;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function looksPathLike(value) {
|
|
554
|
+
const text = String(value || "").trim();
|
|
555
|
+
return /^(?:~|\/|\.\/|\.\.\/|[A-Za-z]:\\)/.test(text) || text.split(/[\\/]/).length >= 3;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function toIso(value) {
|
|
559
|
+
if (!value) return "";
|
|
560
|
+
const date = new Date(value);
|
|
561
|
+
return Number.isNaN(date.getTime()) ? "" : date.toISOString();
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
module.exports = {
|
|
565
|
+
CANONICAL_EVENT_SCHEMA_VERSION,
|
|
566
|
+
EVENT_KINDS,
|
|
567
|
+
isLowSignalToolCall,
|
|
568
|
+
messageToCanonicalEvents,
|
|
569
|
+
normalizeSessionEvents,
|
|
570
|
+
normalizeToolCall,
|
|
571
|
+
normalizeToolResult,
|
|
572
|
+
renderEventText,
|
|
573
|
+
renderToolCallText,
|
|
574
|
+
stableEventId
|
|
575
|
+
};
|