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.
@@ -0,0 +1,349 @@
1
+ "use strict";
2
+
3
+ const { toIso } = require("../archive");
4
+
5
+ function updateClaudeParseContext(event, provider, context = {}) {
6
+ if (!isClaudeProvider(provider) || !event || typeof event !== "object") return;
7
+ const message = event.message || {};
8
+ context.model ||= firstString(event.model, message.model);
9
+ context.sessionId ||= firstString(event.sessionId, event.session_id, event.session?.id);
10
+ context.cwd ||= firstString(event.cwd, event.workingDirectory, event.working_directory);
11
+ }
12
+
13
+ function extractClaudeMessagesFromEvent(event, provider, context = {}) {
14
+ if (!isClaudeProvider(provider) || !event || typeof event !== "object") return [];
15
+ const type = String(event.type || event.kind || event.message?.role || "").toLowerCase();
16
+ const message = event.message && typeof event.message === "object" ? event.message : event;
17
+ const role = normalizeRole(message.role || event.role || type);
18
+ const timestamp = eventTimestamp(event);
19
+
20
+ if (type === "summary" || event.summary) {
21
+ const content = firstString(event.summary, message.summary);
22
+ return content ? [supplementaryMessage(provider, "Claude summary", content, timestamp, "summary")] : [];
23
+ }
24
+
25
+ if (!role || role === "system") return [];
26
+
27
+ const content = message.content ?? event.content;
28
+ if (role === "assistant") return assistantMessages(event, message, provider, context, timestamp, content);
29
+ if (role === "user") return userMessages(event, message, provider, context, timestamp, content);
30
+ return [];
31
+ }
32
+
33
+ function assistantMessages(event, message, provider, context, timestamp, content) {
34
+ const text = visibleText(content);
35
+ const thinking = thinkingText(content);
36
+ const toolCalls = toolUseParts(content).map((part) => normalizeToolCall(part, provider)).filter(Boolean);
37
+ const metadata = {
38
+ provider,
39
+ eventType: firstString(event.type, event.kind),
40
+ model: firstString(message.model, event.model, context.model) || undefined,
41
+ requestId: firstString(event.requestId, event.request_id, message.id) || undefined,
42
+ status: firstString(message.stop_reason, event.stop_reason, event.status) || undefined,
43
+ usage: claudeUsage(message.usage || event.usage) || undefined,
44
+ toolCalls: toolCalls.length ? toolCalls : undefined
45
+ };
46
+ const result = [];
47
+ if (thinking) result.push(supplementaryMessage(provider, "Claude thinking", thinking, timestamp, "thinking", metadata.model));
48
+ if (text || toolCalls.length) result.push({ role: "assistant", content: text, timestamp, metadata });
49
+ return result;
50
+ }
51
+
52
+ function userMessages(event, message, provider, context, timestamp, content) {
53
+ const text = visibleText(content);
54
+ const toolResults = toolResultParts(content).map((part) => normalizeToolResult(part, provider, event.toolUseResult)).filter(Boolean);
55
+ const result = [];
56
+ if (text) {
57
+ result.push({
58
+ role: "user",
59
+ content: text,
60
+ timestamp,
61
+ metadata: {
62
+ provider,
63
+ eventType: firstString(event.type, event.kind),
64
+ model: firstString(message.model, event.model, context.model) || undefined
65
+ }
66
+ });
67
+ }
68
+ for (let index = 0; index < toolResults.length; index++) {
69
+ const toolResult = toolResults[index];
70
+ result.push({
71
+ role: "tool",
72
+ content: toolResult.output,
73
+ timestamp: offsetTimestamp(timestamp, index + 1),
74
+ metadata: {
75
+ provider,
76
+ eventType: "claude-tool-result",
77
+ toolResult
78
+ }
79
+ });
80
+ }
81
+ return result;
82
+ }
83
+
84
+ function supplementaryMessage(provider, title, content, timestamp, summaryKind, model = "") {
85
+ return {
86
+ role: "assistant",
87
+ content: `### ${title}\n\n${String(content || "").trim()}`,
88
+ timestamp,
89
+ metadata: {
90
+ provider,
91
+ eventType: `claude-${summaryKind}`,
92
+ supplementary: true,
93
+ summaryKind,
94
+ model: model || undefined
95
+ }
96
+ };
97
+ }
98
+
99
+ function toolUseParts(content) {
100
+ return arrayContent(content).filter((part) => {
101
+ const type = String(part?.type || part?.kind || "").toLowerCase();
102
+ return type === "tool_use" || type === "server_tool_use" || type === "mcp_tool_use" || type === "function_call";
103
+ });
104
+ }
105
+
106
+ function toolResultParts(content) {
107
+ return arrayContent(content).filter((part) => {
108
+ const type = String(part?.type || part?.kind || "").toLowerCase();
109
+ return type === "tool_result" || type === "function_result" || type === "tool_output";
110
+ });
111
+ }
112
+
113
+ function arrayContent(content) {
114
+ return Array.isArray(content) ? content.filter((part) => part && typeof part === "object") : [];
115
+ }
116
+
117
+ function visibleText(content) {
118
+ if (!Array.isArray(content)) return extractText(content).trim();
119
+ return content
120
+ .filter((part) => {
121
+ const type = String(part?.type || part?.kind || "").toLowerCase();
122
+ return !/(tool_use|tool_result|function_call|function_result|thinking|redacted_thinking)/.test(type);
123
+ })
124
+ .map((part) => extractText(part))
125
+ .filter(Boolean)
126
+ .join("\n")
127
+ .trim();
128
+ }
129
+
130
+ function thinkingText(content) {
131
+ if (!Array.isArray(content)) return "";
132
+ return content
133
+ .filter((part) => /thinking/.test(String(part?.type || part?.kind || "").toLowerCase()))
134
+ .map((part) => firstString(part.thinking, part.text, part.content, part.summary))
135
+ .filter(Boolean)
136
+ .join("\n\n")
137
+ .trim();
138
+ }
139
+
140
+ function normalizeToolCall(part, provider) {
141
+ const rawName = firstString(part.name, part.tool_name, part.toolName, part.function?.name, "tool");
142
+ const rawArgs = parseArgs(part.input ?? part.arguments ?? part.args ?? part.function?.arguments);
143
+ const command = toolCommandText(rawArgs);
144
+ const patch = extractApplyPatchFromCommand(command);
145
+ const name = patch && /bash|shell|command|exec|terminal/i.test(rawName) ? "apply_patch" : rawName;
146
+ const args = patch && rawArgs && typeof rawArgs === "object" && !Array.isArray(rawArgs)
147
+ ? {
148
+ ...rawArgs,
149
+ patch,
150
+ diff: patch,
151
+ path: rawArgs.path || rawArgs.file_path || rawArgs.file || patchTargetPath(patch) || undefined
152
+ }
153
+ : rawArgs;
154
+ const summary = summarizeArgs(args);
155
+ const category = patch ? "edit" : toolCategory(name, part.type);
156
+ return {
157
+ id: firstString(part.id, part.tool_use_id, part.call_id, part.callId) || undefined,
158
+ name,
159
+ displayName: displayName(name),
160
+ rawCategory: firstString(part.type, part.kind) || undefined,
161
+ category,
162
+ title: firstString(part.title, displayName(name)),
163
+ status: firstString(part.status, "tool_call"),
164
+ argument: summary,
165
+ rawInputSummary: summary,
166
+ inputPreview: summary,
167
+ target: toolTarget(args) || undefined,
168
+ arguments: args && typeof args === "object" && !Array.isArray(args) ? args : undefined,
169
+ provider
170
+ };
171
+ }
172
+
173
+ function normalizeToolResult(part, provider, eventToolUseResult) {
174
+ const output = extractToolResultOutput(part, eventToolUseResult);
175
+ if (!output) return null;
176
+ const name = firstString(part.name, part.tool_name, part.toolName, part.tool_use_id, "Tool output");
177
+ const category = toolCategory(name, part.type);
178
+ return {
179
+ provider,
180
+ id: firstString(part.tool_use_id, part.id, part.call_id, part.callId) || undefined,
181
+ kind: displayName(name),
182
+ title: part.is_error ? "Tool error" : "Tool result",
183
+ rawCategory: firstString(part.type, part.kind) || undefined,
184
+ category,
185
+ categoryLabel: displayName(category),
186
+ summary: firstLine(output),
187
+ output,
188
+ lineCount: output.split("\n").length,
189
+ collapsed: output.split("\n").length > 18,
190
+ status: part.is_error ? "error" : "completed"
191
+ };
192
+ }
193
+
194
+ function extractToolResultOutput(part, eventToolUseResult) {
195
+ const direct = extractText(part.content ?? part.output ?? part.result ?? part.text);
196
+ const extra = extractText(eventToolUseResult?.stdout ?? eventToolUseResult?.stderr ?? eventToolUseResult?.output ?? eventToolUseResult?.content);
197
+ return [direct, extra].filter(Boolean).join("\n").trim();
198
+ }
199
+
200
+ function claudeUsage(usage) {
201
+ if (!usage || typeof usage !== "object") return null;
202
+ const input = numberValue(usage.input_tokens, usage.inputTokens, usage.prompt_tokens);
203
+ const output = numberValue(usage.output_tokens, usage.outputTokens, usage.completion_tokens);
204
+ const cacheCreation = numberValue(usage.cache_creation_input_tokens, usage.cacheCreationInputTokens);
205
+ const cacheRead = numberValue(usage.cache_read_input_tokens, usage.cacheReadInputTokens);
206
+ if ([input, output, cacheCreation, cacheRead].every((value) => value == null)) return null;
207
+ return {
208
+ inputTokens: input ?? undefined,
209
+ outputTokens: output ?? undefined,
210
+ cacheCreationInputTokens: cacheCreation ?? undefined,
211
+ cacheReadInputTokens: cacheRead ?? undefined,
212
+ totalTokens: [input, output, cacheCreation, cacheRead].reduce((sum, value) => sum + (value || 0), 0)
213
+ };
214
+ }
215
+
216
+ function extractText(value, depth = 0) {
217
+ if (value == null || depth > 6) return "";
218
+ if (typeof value === "string") return value;
219
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
220
+ if (Array.isArray(value)) return value.map((item) => extractText(item, depth + 1)).filter(Boolean).join("\n");
221
+ if (typeof value === "object") {
222
+ if (typeof value.text === "string") return value.text;
223
+ if (typeof value.content === "string") return value.content;
224
+ if (Array.isArray(value.content)) return extractText(value.content, depth + 1);
225
+ if (Array.isArray(value.parts)) return extractText(value.parts, depth + 1);
226
+ if (value.message) return extractText(value.message, depth + 1);
227
+ }
228
+ return "";
229
+ }
230
+
231
+ function parseArgs(value) {
232
+ if (value == null) return {};
233
+ if (typeof value === "string") {
234
+ try {
235
+ return JSON.parse(value);
236
+ } catch {
237
+ return value;
238
+ }
239
+ }
240
+ return value;
241
+ }
242
+
243
+ function summarizeArgs(value) {
244
+ if (value == null) return "";
245
+ if (typeof value === "string") return value.slice(0, 240);
246
+ if (typeof value !== "object") return String(value).slice(0, 240);
247
+ for (const key of ["command", "cmd", "query", "pattern", "path", "file_path", "url"]) {
248
+ if (value[key]) return String(value[key]).slice(0, 240);
249
+ }
250
+ return Object.entries(value)
251
+ .slice(0, 3)
252
+ .map(([key, item]) => `${key}: ${typeof item === "string" ? item : JSON.stringify(item)}`)
253
+ .join(", ")
254
+ .slice(0, 240);
255
+ }
256
+
257
+ function toolTarget(args) {
258
+ if (!args || typeof args !== "object" || Array.isArray(args)) return "";
259
+ return firstString(args.path, args.file_path, args.file, args.url);
260
+ }
261
+
262
+ function toolCategory(name, type) {
263
+ const key = `${name || ""} ${type || ""}`.toLowerCase();
264
+ if (/edit|write|patch|replace|update|notebook/.test(key)) return "edit";
265
+ if (/bash|shell|command|exec|terminal/.test(key)) return "shell";
266
+ if (/read|open|view|list|ls/.test(key)) return "read";
267
+ if (/grep|glob|find|search|rg/.test(key)) return "search";
268
+ if (/web|fetch|url|browser/.test(key)) return "web";
269
+ if (/mcp/.test(key)) return "mcp";
270
+ return "tool";
271
+ }
272
+
273
+ function toolCommandText(args) {
274
+ if (typeof args === "string") return args;
275
+ if (!args || typeof args !== "object" || Array.isArray(args)) return "";
276
+ const command = args.command ?? args.cmd ?? args.script;
277
+ if (Array.isArray(command)) return command.filter((part) => typeof part === "string").join(" ");
278
+ return firstString(command);
279
+ }
280
+
281
+ function extractApplyPatchFromCommand(command) {
282
+ const text = String(command || "");
283
+ if (!/\bapply_patch\b/.test(text) && !/\*\*\* Begin Patch/.test(text)) return "";
284
+ const heredoc = text.match(/apply_patch\s*<<\s*['"]?([A-Za-z0-9_-]+)['"]?\s*\n([\s\S]*?)\n\1\b/);
285
+ if (heredoc) return heredoc[2].trim();
286
+ const begin = text.indexOf("*** Begin Patch");
287
+ const end = text.indexOf("*** End Patch");
288
+ if (begin >= 0 && end >= begin) return text.slice(begin, end + "*** End Patch".length).trim();
289
+ return "";
290
+ }
291
+
292
+ function patchTargetPath(patch) {
293
+ const match = String(patch || "").match(/^\*\*\* (?:Update|Add|Delete) File:\s+(.+)$/m);
294
+ return match ? match[1].trim() : "";
295
+ }
296
+
297
+ function displayName(value) {
298
+ return String(value || "tool")
299
+ .split(/_+/g)
300
+ .map((part) => part.replace(/(^|[-\s])([a-z])/g, (_, prefix, char) => `${prefix}${char.toUpperCase()}`))
301
+ .join(" ");
302
+ }
303
+
304
+ function eventTimestamp(event) {
305
+ return toIso(event.timestamp || event.created_at || event.createdAt || event.time || event.message?.timestamp) || "";
306
+ }
307
+
308
+ function offsetTimestamp(timestamp, offset = 0) {
309
+ const base = Date.parse(timestamp);
310
+ if (!Number.isFinite(base)) return timestamp || "";
311
+ return new Date(base + offset).toISOString();
312
+ }
313
+
314
+ function normalizeRole(role) {
315
+ const value = String(role || "").toLowerCase();
316
+ if (value === "human" || value.includes("user")) return "user";
317
+ if (value === "assistant" || value.includes("assistant")) return "assistant";
318
+ if (value === "system") return "system";
319
+ return "";
320
+ }
321
+
322
+ function numberValue(...values) {
323
+ for (const value of values) {
324
+ if (value == null || value === "") continue;
325
+ const number = Number(value);
326
+ if (Number.isFinite(number)) return number;
327
+ }
328
+ return null;
329
+ }
330
+
331
+ function firstString(...values) {
332
+ for (const value of values) {
333
+ if (typeof value === "string" && value.trim()) return value.trim();
334
+ }
335
+ return "";
336
+ }
337
+
338
+ function firstLine(value) {
339
+ return String(value || "").split(/\r?\n/).find((line) => line.trim())?.trim() || "";
340
+ }
341
+
342
+ function isClaudeProvider(provider) {
343
+ return provider === "claude_code" || provider === "claude_sdk" || provider === "claude_desktop";
344
+ }
345
+
346
+ module.exports = {
347
+ extractClaudeMessagesFromEvent,
348
+ updateClaudeParseContext
349
+ };