agentel 0.2.6 → 0.3.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,396 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+
5
+ const PROVIDER = "factory";
6
+
7
+ function parseFactorySessionFile(file, options = {}) {
8
+ let text;
9
+ try {
10
+ text = fs.readFileSync(file, "utf8");
11
+ } catch {
12
+ return null;
13
+ }
14
+ return parseFactorySessionText(text, options);
15
+ }
16
+
17
+ // Factory droid transcripts are JSONL: a `session_start` header line followed
18
+ // by `message` wrapper lines whose payloads use Anthropic-style content blocks.
19
+ // Block keys drift between snake_case and camelCase across droid versions
20
+ // (tool_use_id vs toolUseId, media_type vs mediaType), so accept both.
21
+ function parseFactorySessionText(text, options = {}) {
22
+ const lines = String(text || "").split(/\r?\n/);
23
+ let header = null;
24
+ const messages = [];
25
+ const fallbackTime = toIso(options.fallbackTime) || new Date().toISOString();
26
+ let index = 0;
27
+ for (const line of lines) {
28
+ if (!line.trim()) continue;
29
+ const entry = parseJson(line);
30
+ if (!entry || typeof entry !== "object") continue;
31
+ if (entry.type === "session_start") {
32
+ header ||= entry;
33
+ continue;
34
+ }
35
+ if (!header) continue;
36
+ if (entry.type === "todo_state") continue;
37
+ if (entry.type !== "message" || !entry.message || typeof entry.message !== "object") continue;
38
+ messages.push(...factoryEntryMessages(entry, { fallbackTime, index }));
39
+ index++;
40
+ }
41
+ if (!header || !firstString(header.id)) return null;
42
+ return {
43
+ header,
44
+ sessionId: firstString(header.id),
45
+ title: firstString(header.sessionTitle, header.title),
46
+ cwd: firstString(header.cwd, header.lastCwd),
47
+ messages,
48
+ startedAt: messages[0]?.timestamp || fallbackTime,
49
+ endedAt: messages[messages.length - 1]?.timestamp || fallbackTime
50
+ };
51
+ }
52
+
53
+ function factoryEntryMessages(entry, context) {
54
+ const payload = entry.message;
55
+ const role = normalizeFactoryRole(payload.role);
56
+ const timestamp =
57
+ toIso(entry.timestamp) ||
58
+ toIso(payload.createdAt) ||
59
+ toIso(payload.timestamp) ||
60
+ offsetTimestamp(context.fallbackTime, context.index);
61
+ const blocks = normalizeFactoryContent(payload.content);
62
+ const texts = [];
63
+ const thinking = [];
64
+ const toolCalls = [];
65
+ const toolResults = [];
66
+ for (const block of blocks) {
67
+ if (!block || typeof block !== "object") continue;
68
+ const type = String(block.type || "");
69
+ if (type === "text" && typeof block.text === "string") texts.push(block.text);
70
+ else if (type === "thinking") thinking.push(firstString(block.thinking, block.text));
71
+ else if (type === "redacted_thinking") thinking.push("[redacted thinking]");
72
+ else if (type === "tool_use") {
73
+ const call = factoryToolCall(block);
74
+ if (call) toolCalls.push(call);
75
+ } else if (type === "tool_result") {
76
+ const result = factoryToolResult(block);
77
+ if (result) toolResults.push(result);
78
+ } else if (type === "image" || type === "document") {
79
+ texts.push(factoryAttachmentLabel(block));
80
+ }
81
+ }
82
+ const result = [];
83
+ const content = texts.filter(Boolean).join("\n").trim();
84
+ const thinkingText = thinking.filter(Boolean).join("\n\n").trim();
85
+ if (content || toolCalls.length || thinkingText) {
86
+ result.push({
87
+ role: role === "tool" ? "assistant" : role,
88
+ content,
89
+ timestamp,
90
+ metadata: compactObject({
91
+ provider: PROVIDER,
92
+ providerMessageId: firstString(entry.id),
93
+ parentMessageId: firstString(entry.parentId),
94
+ model: role === "assistant" ? firstString(payload.model) : undefined,
95
+ thinking: thinkingText || undefined,
96
+ visibility: factoryVisibility(payload.visibility),
97
+ isError: payload.isError === true || undefined,
98
+ userMessageSource: firstString(payload.userMessageSource) || undefined,
99
+ toolCalls: toolCalls.length ? toolCalls : undefined
100
+ })
101
+ });
102
+ }
103
+ for (const toolResult of toolResults) {
104
+ result.push({
105
+ role: "tool",
106
+ content: toolResult.output,
107
+ timestamp,
108
+ metadata: compactObject({
109
+ provider: PROVIDER,
110
+ providerMessageId: firstString(entry.id),
111
+ toolResult
112
+ })
113
+ });
114
+ }
115
+ return result;
116
+ }
117
+
118
+ function factoryToolCall(block) {
119
+ const name = firstString(block.name);
120
+ if (!name) return null;
121
+ const args = block.input && typeof block.input === "object" ? block.input : parseJson(block.input) || undefined;
122
+ const summary = summarizeToolArguments(args);
123
+ return compactObject({
124
+ id: firstString(block.id) || undefined,
125
+ name,
126
+ displayName: toolDisplayName(name),
127
+ rawCategory: "tool_use",
128
+ category: factoryToolCategory(name),
129
+ title: toolDisplayName(name),
130
+ status: "tool_call",
131
+ argument: summary,
132
+ rawInputSummary: summary,
133
+ inputPreview: summary,
134
+ target: toolTarget(args) || undefined,
135
+ arguments: args,
136
+ provider: PROVIDER
137
+ });
138
+ }
139
+
140
+ function factoryToolResult(block) {
141
+ const id = firstString(block.tool_use_id, block.toolUseId);
142
+ const parsedContent = factoryToolResultContent(block.content);
143
+ const detail = parsedContent.detail;
144
+ const output = firstString(detail?.diff) || parsedContent.text || (detail ? JSON.stringify(detail) : "");
145
+ if (!output && !id) return null;
146
+ const isError = block.isError === true || block.is_error === true || detail?.success === false;
147
+ const lineCount = output ? output.split(/\r?\n/).length : 0;
148
+ const structuredPatch = factoryStructuredPatch(detail);
149
+ return compactObject({
150
+ provider: PROVIDER,
151
+ id: id || undefined,
152
+ kind: "Tool result",
153
+ title: isError ? "Tool error" : "Tool result",
154
+ rawCategory: "tool_result",
155
+ category: "tool",
156
+ summary: firstLine(output),
157
+ output,
158
+ lineCount: lineCount || undefined,
159
+ collapsed: lineCount > 18 || undefined,
160
+ status: isError ? "error" : "completed",
161
+ structuredPatch: structuredPatch.length ? structuredPatch : undefined
162
+ });
163
+ }
164
+
165
+ function factoryToolResultContent(content) {
166
+ if (typeof content === "string") {
167
+ const detail = parseJson(content);
168
+ if (detail && typeof detail === "object" && !Array.isArray(detail) && (detail.diff || detail.diffLines || detail.success !== undefined)) {
169
+ return { text: firstString(detail.content, detail.message), detail };
170
+ }
171
+ return { text: content, detail: null };
172
+ }
173
+ if (Array.isArray(content)) {
174
+ const texts = content
175
+ .map((item) => {
176
+ if (typeof item === "string") return item;
177
+ if (item && typeof item === "object" && typeof item.text === "string") return item.text;
178
+ if (item && typeof item === "object" && (item.type === "image" || item.type === "document")) return factoryAttachmentLabel(item);
179
+ return "";
180
+ })
181
+ .filter(Boolean);
182
+ return { text: texts.join("\n"), detail: null };
183
+ }
184
+ if (content && typeof content === "object") {
185
+ if (content.diff || content.diffLines || content.success !== undefined) {
186
+ return { text: firstString(content.content, content.message), detail: content };
187
+ }
188
+ return { text: JSON.stringify(content), detail: null };
189
+ }
190
+ return { text: "", detail: null };
191
+ }
192
+
193
+ // Factory file-op results store per-line diffs as
194
+ // {type: added|removed|unchanged|context, content, lineNumber: {old?, new?}}.
195
+ function factoryStructuredPatch(detail) {
196
+ const diffLines = Array.isArray(detail?.diffLines) ? detail.diffLines : [];
197
+ if (!diffLines.length) return [];
198
+ const lines = [];
199
+ let oldStart = 0;
200
+ let newStart = 0;
201
+ for (const item of diffLines) {
202
+ if (!item || typeof item !== "object") continue;
203
+ const type = String(item.type || "");
204
+ const prefix = type === "added" ? "+" : type === "removed" ? "-" : " ";
205
+ lines.push(`${prefix}${String(item.content ?? "")}`);
206
+ const oldLine = Number(item.lineNumber?.old);
207
+ const newLine = Number(item.lineNumber?.new);
208
+ if (!oldStart && Number.isFinite(oldLine) && oldLine > 0) oldStart = oldLine;
209
+ if (!newStart && Number.isFinite(newLine) && newLine > 0) newStart = newLine;
210
+ }
211
+ if (!lines.length) return [];
212
+ const hunk = { oldStart: oldStart || 1, newStart: newStart || 1, lines };
213
+ const file = firstString(detail?.file_path, detail?.filePath);
214
+ if (file) hunk.file = file;
215
+ return [hunk];
216
+ }
217
+
218
+ // Sidecar <sessionId>.settings.json carries aggregate token usage and session
219
+ // configuration; there is no per-message usage in droid transcripts.
220
+ function factorySessionSummary(settings, header) {
221
+ if (!settings && !header) return null;
222
+ const tokenUsage = settings?.tokenUsage && typeof settings.tokenUsage === "object" ? settings.tokenUsage : null;
223
+ const usage = factoryUsage(tokenUsage);
224
+ const model = cleanFactoryModel(firstString(settings?.model));
225
+ return compactObject({
226
+ usage: usage || undefined,
227
+ modelUsage: model ? [{ model, source: "factory-session-settings" }] : undefined,
228
+ factoryDroid: compactObject({
229
+ model: firstString(settings?.model) || undefined,
230
+ reasoningEffort: firstString(settings?.reasoningEffort) || undefined,
231
+ autonomyMode: firstString(settings?.autonomyMode, settings?.autonomyLevel) || undefined,
232
+ specModeModel: firstString(settings?.specModeModel) || undefined,
233
+ providerLock: firstString(settings?.providerLock) || undefined,
234
+ apiProviderLock: firstString(settings?.apiProviderLock) || undefined,
235
+ factoryCredits: positiveNumber(tokenUsage?.factoryCredits),
236
+ assistantActiveTimeMs: positiveNumber(settings?.assistantActiveTimeMs),
237
+ archivedAt: toIso(settings?.archivedAt) || undefined,
238
+ sessionType: firstString(header?.decompSessionType) || undefined,
239
+ callingSessionId: firstString(header?.callingSessionId) || undefined,
240
+ parentSessionId: firstString(header?.parent) || undefined,
241
+ owner: firstString(header?.owner) || undefined,
242
+ subagentTokenUsageBySessionId:
243
+ settings?.childInclusiveTokenUsageBySessionId && typeof settings.childInclusiveTokenUsageBySessionId === "object" && Object.keys(settings.childInclusiveTokenUsageBySessionId).length
244
+ ? settings.childInclusiveTokenUsageBySessionId
245
+ : undefined
246
+ })
247
+ });
248
+ }
249
+
250
+ function factoryUsage(tokenUsage) {
251
+ if (!tokenUsage) return null;
252
+ const inputTokens = positiveNumber(tokenUsage.inputTokens);
253
+ const outputTokens = positiveNumber(tokenUsage.outputTokens);
254
+ const cacheReadTokens = positiveNumber(tokenUsage.cacheReadTokens ?? tokenUsage.cacheReadInputTokens);
255
+ const cacheCreationTokens = positiveNumber(tokenUsage.cacheCreationTokens ?? tokenUsage.cacheCreationInputTokens);
256
+ if (!inputTokens && !outputTokens && !cacheReadTokens && !cacheCreationTokens) return null;
257
+ return compactObject({
258
+ inputTokens,
259
+ outputTokens,
260
+ cacheReadTokens: cacheReadTokens || undefined,
261
+ cacheCreationInputTokens: cacheCreationTokens || undefined,
262
+ source: "factory-session-settings"
263
+ });
264
+ }
265
+
266
+ // BYOK model ids look like "custom:Claude-Opus-4.5-Thinking-[Anthropic]-0".
267
+ function cleanFactoryModel(model) {
268
+ if (!model) return "";
269
+ return model
270
+ .replace(/^custom:/, "")
271
+ .replace(/-?\[[^\]]*\](-\d+)?$/, "")
272
+ .trim();
273
+ }
274
+
275
+ function factoryVisibility(value) {
276
+ const text = firstString(value);
277
+ return text && text !== "both" ? text : undefined;
278
+ }
279
+
280
+ function normalizeFactoryRole(role) {
281
+ const text = String(role || "").toLowerCase();
282
+ if (text === "user" || text === "assistant" || text === "system" || text === "tool") return text;
283
+ return "assistant";
284
+ }
285
+
286
+ function normalizeFactoryContent(content) {
287
+ if (typeof content === "string") return content.trim() ? [{ type: "text", text: content }] : [];
288
+ if (Array.isArray(content)) return content;
289
+ if (content && typeof content === "object") return [content];
290
+ return [];
291
+ }
292
+
293
+ function factoryAttachmentLabel(block) {
294
+ const mime = firstString(block?.source?.media_type, block?.source?.mediaType, block?.media_type, block?.mediaType);
295
+ return mime ? `[Attachment: ${mime}]` : "[Attachment]";
296
+ }
297
+
298
+ function factoryToolCategory(name) {
299
+ const text = String(name || "");
300
+ if (/^task$/i.test(text) || /subagent/i.test(text)) return "task";
301
+ if (/execute|bash|shell|command/i.test(text)) return "shell";
302
+ if (/websearch|fetchurl|web|url|http/i.test(text)) return "web";
303
+ if (/edit|create|write|applypatch|patch|todowrite/i.test(text)) return "edit";
304
+ if (/^read$|^ls$|view/i.test(text)) return "read";
305
+ if (/grep|glob|find|search/i.test(text)) return "search";
306
+ if (/^mcp__/i.test(text)) return "mcp";
307
+ return "tool";
308
+ }
309
+
310
+ function summarizeToolArguments(value) {
311
+ if (value == null) return "";
312
+ if (typeof value === "string") return value.slice(0, 240);
313
+ if (typeof value !== "object") return String(value).slice(0, 240);
314
+ for (const key of ["command", "prompt", "query", "pattern", "file_path", "path", "url", "skill", "plan"]) {
315
+ if (value[key]) return String(value[key]).slice(0, 240);
316
+ }
317
+ return Object.entries(value)
318
+ .slice(0, 3)
319
+ .map(([key, item]) => `${key}: ${typeof item === "string" ? item : JSON.stringify(item)}`)
320
+ .join(", ")
321
+ .slice(0, 240);
322
+ }
323
+
324
+ function toolTarget(args) {
325
+ if (!args || typeof args !== "object") return "";
326
+ return firstString(args.file_path, args.filePath, args.path, args.directory_path, args.folder, args.url);
327
+ }
328
+
329
+ function toolDisplayName(value) {
330
+ const text = String(value || "tool").trim();
331
+ return text
332
+ .split(/_+|(?=[A-Z])/g)
333
+ .filter(Boolean)
334
+ .map((part) => part.replace(/(^|[-\s])([a-z])/g, (_, prefix, char) => `${prefix}${char.toUpperCase()}`))
335
+ .join(" ");
336
+ }
337
+
338
+ function firstLine(value) {
339
+ return String(value || "").split(/\r?\n/).find((line) => line.trim())?.trim() || "";
340
+ }
341
+
342
+ function offsetTimestamp(base, index) {
343
+ const date = new Date(base);
344
+ if (Number.isNaN(date.valueOf())) return base;
345
+ return new Date(date.valueOf() + index * 1000).toISOString();
346
+ }
347
+
348
+ function toIso(value) {
349
+ if (!value) return "";
350
+ if (typeof value === "number") {
351
+ const ms = value > 1e12 ? value : value * 1000;
352
+ const date = new Date(ms);
353
+ return Number.isNaN(date.valueOf()) ? "" : date.toISOString();
354
+ }
355
+ const date = new Date(value);
356
+ return Number.isNaN(date.valueOf()) ? "" : date.toISOString();
357
+ }
358
+
359
+ function parseJson(text) {
360
+ if (typeof text !== "string" || !text.trim()) return null;
361
+ try {
362
+ return JSON.parse(text);
363
+ } catch {
364
+ return null;
365
+ }
366
+ }
367
+
368
+ function firstString(...values) {
369
+ for (const value of values) {
370
+ if (typeof value === "string" && value.trim()) return value.trim();
371
+ }
372
+ return "";
373
+ }
374
+
375
+ function positiveNumber(value) {
376
+ const number = Number(value);
377
+ return Number.isFinite(number) && number > 0 ? number : undefined;
378
+ }
379
+
380
+ function compactObject(value) {
381
+ if (!value || typeof value !== "object") return value;
382
+ const result = {};
383
+ for (const [key, item] of Object.entries(value)) {
384
+ if (item === undefined || item === null || item === "") continue;
385
+ result[key] = item;
386
+ }
387
+ return Object.keys(result).length ? result : undefined;
388
+ }
389
+
390
+ module.exports = {
391
+ cleanFactoryModel,
392
+ factorySessionSummary,
393
+ factoryStructuredPatch,
394
+ parseFactorySessionFile,
395
+ parseFactorySessionText
396
+ };
@@ -425,6 +425,7 @@ function normalizeGeminiToolCall(node, { event, part } = {}) {
425
425
  (node.code ? { language: node.language, code: node.code } : undefined)
426
426
  );
427
427
  const summary = summarizeToolArguments(args);
428
+ const subagent = geminiSubagentToolMetadata(name, node, args, event);
428
429
  return {
429
430
  id: firstString(node.id, node.callId, node.call_id, event?.callId, event?.call_id, part?.id) || undefined,
430
431
  name,
@@ -438,10 +439,41 @@ function normalizeGeminiToolCall(node, { event, part } = {}) {
438
439
  inputPreview: summary,
439
440
  target: toolTarget(args) || undefined,
440
441
  arguments: args && typeof args === "object" && !Array.isArray(args) ? args : undefined,
442
+ agentId: subagent.agentId,
443
+ agentName: subagent.agentName,
444
+ prompt: subagent.prompt,
445
+ subagentProgress: subagent.subagentProgress,
446
+ resultPreview: subagent.resultPreview,
441
447
  provider: PROVIDER
442
448
  };
443
449
  }
444
450
 
451
+ function geminiSubagentToolMetadata(name, node = {}, args = {}, event = {}) {
452
+ if (!geminiIsSubagentToolName(name)) return {};
453
+ const resultDisplay = node.resultDisplay || node.result_display || event.resultDisplay || event.result_display || {};
454
+ const progress = resultDisplay && typeof resultDisplay === "object"
455
+ ? compactMetadata({
456
+ isSubagentProgress: resultDisplay.isSubagentProgress,
457
+ agentName: firstString(resultDisplay.agentName, resultDisplay.agent_name),
458
+ state: firstString(resultDisplay.state, resultDisplay.status),
459
+ terminateReason: firstString(resultDisplay.terminateReason, resultDisplay.terminate_reason),
460
+ result: previewString(firstString(resultDisplay.result, resultDisplay.output, resultDisplay.summary), 600),
461
+ recentActivity: Array.isArray(resultDisplay.recentActivity) ? resultDisplay.recentActivity.slice(0, 8) : undefined
462
+ })
463
+ : null;
464
+ return compactMetadata({
465
+ agentId: firstString(node.agentId, node.agent_id, event.agentId, event.agent_id, args?.agentId, args?.agent_id),
466
+ agentName: firstString(node.agentName, node.agent_name, resultDisplay.agentName, resultDisplay.agent_name, args?.agent_name, args?.agentName, args?.name),
467
+ prompt: firstString(args?.prompt, args?.task, args?.instructions),
468
+ subagentProgress: progress,
469
+ resultPreview: previewString(firstString(node.result, event.result, resultDisplay.result, resultDisplay.output, resultDisplay.summary), 600)
470
+ });
471
+ }
472
+
473
+ function geminiIsSubagentToolName(name) {
474
+ return /^(invoke[_-]?agent|run[_-]?subagent|invoke[_-]?subagent)$/i.test(String(name || ""));
475
+ }
476
+
445
477
  function normalizeGeminiToolResult(node, { event, part } = {}) {
446
478
  if (!node || typeof node !== "object") return null;
447
479
  const name = firstString(
@@ -607,6 +639,7 @@ function geminiUsage(value) {
607
639
  totalTokens,
608
640
  thoughtsTokens,
609
641
  cacheReadTokens,
642
+ cacheReadTokensIncludedInInput: cacheReadTokens ? true : undefined,
610
643
  toolUsePromptTokens
611
644
  });
612
645
  return Object.keys(normalized).length ? normalized : null;
@@ -880,7 +913,7 @@ function geminiSessionSummaryUsage(summary) {
880
913
  cacheInputTokens += numberValue(model.cacheReadTokens) || 0;
881
914
  }
882
915
  if (!inputTokens && !outputTokens && !cacheInputTokens) return null;
883
- return { inputTokens, outputTokens, cacheInputTokens, totalTokens: inputTokens + outputTokens };
916
+ return { inputTokens, outputTokens, cacheInputTokens, cacheInputTokensIncludedInInput: cacheInputTokens ? true : undefined, totalTokens: inputTokens + outputTokens };
884
917
  }
885
918
 
886
919
  function mergeGeminiSessionSummaries(summaries) {
@@ -998,6 +1031,7 @@ function normalizeGeminiRole(role) {
998
1031
 
999
1032
  function toolCategory(name, type = "") {
1000
1033
  const text = `${name || ""} ${type || ""}`;
1034
+ if (/invoke[_-]?agent|subagent/i.test(text)) return "task";
1001
1035
  if (/shell|bash|command|terminal|exec|npm|yarn|pnpm/i.test(text)) return "shell";
1002
1036
  if (/web|fetch|browser|url|http/i.test(text)) return "web";
1003
1037
  if (/edit|write|patch|replace|insert|update/i.test(text)) return "edit";
@@ -1126,6 +1160,12 @@ function asArray(value) {
1126
1160
  return value == null ? [] : [value];
1127
1161
  }
1128
1162
 
1163
+ function previewString(value, max = 600) {
1164
+ const text = String(value || "").replace(/\s+/g, " ").trim();
1165
+ if (!text) return "";
1166
+ return text.length > max ? `${text.slice(0, max - 3).trimEnd()}...` : text;
1167
+ }
1168
+
1129
1169
  function compactMetadata(value) {
1130
1170
  const output = {};
1131
1171
  for (const [key, item] of Object.entries(value || {})) {