agentel 0.2.5 → 0.2.8

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/src/config.js CHANGED
@@ -45,6 +45,7 @@ function defaultConfig(env = process.env) {
45
45
  },
46
46
  imports: {
47
47
  defaultSinceDays: 30,
48
+ updateSince: "",
48
49
  sources: IMPORT_SOURCE_ORDER,
49
50
  autoDiscoverSources: true
50
51
  },
@@ -246,6 +247,7 @@ function parseConfigValue(value) {
246
247
  function normalizeConfigKeyValue(key, value) {
247
248
  if (key === "imports.sources") return normalizeImportSources(value);
248
249
  if (key === "imports.defaultSinceDays") return normalizeNonNegativeInteger(value, 30);
250
+ if (key === "imports.updateSince") return normalizeImportSinceValue(value);
249
251
  if (key === "sync.intervalMinutes" || key === "index.intervalMinutes" || key === "index.batteryIntervalMinutes") {
250
252
  return normalizeSyncInterval(value);
251
253
  }
@@ -253,6 +255,15 @@ function normalizeConfigKeyValue(key, value) {
253
255
  return value;
254
256
  }
255
257
 
258
+ function normalizeImportSinceValue(value) {
259
+ const text = String(value || "").trim().toLowerCase();
260
+ if (!text) return "";
261
+ if (text === "all") return "all";
262
+ const match = text.match(/^(\d+)([dhm])$/);
263
+ if (!match) return text;
264
+ return `${Math.max(1, Math.round(Number(match[1])))}${match[2]}`;
265
+ }
266
+
256
267
  function normalizeImportSources(value) {
257
268
  const raw = Array.isArray(value)
258
269
  ? value
package/src/doctor.js CHANGED
@@ -59,6 +59,7 @@ function sourceCoverage(discovery, cfg) {
59
59
  const rows = [
60
60
  coverageRow("codex-cli", "Codex CLI", discovery.codexCli, configured),
61
61
  coverageRow("codex-desktop", "Codex Desktop", discovery.codexDesktop, configured),
62
+ coverageRow("codex-sdk", "Codex SDK jobs", discovery.codexSdk, configured),
62
63
  coverageRow("claude", "Claude Code CLI", discovery.claude, configured),
63
64
  coverageRow("claude-code-desktop", "Claude Code Desktop", discovery.claudeCodeDesktop, configured),
64
65
  coverageRow("claude-workspace", "Claude Workspace", discovery.claudeWorkspace, configured),
@@ -194,6 +195,7 @@ function parserUpdateCommand(sourceType) {
194
195
  const source = {
195
196
  "codex-cli-history": "codex-cli",
196
197
  "codex-desktop-history": "codex-desktop",
198
+ "codex-sdk-history": "codex-sdk",
197
199
  "cli-history": "claude",
198
200
  "claude-sdk-history": "claude-sdk",
199
201
  "claude-code-desktop-metadata": "claude-code-desktop",
@@ -17,6 +17,9 @@ function extractClaudeMessagesFromEvent(event, provider, context = {}) {
17
17
  const role = normalizeRole(message.role || event.role || type);
18
18
  const timestamp = eventTimestamp(event);
19
19
 
20
+ const remoteMessages = remoteControlLifecycleMessages(event, provider, context, type, timestamp);
21
+ if (remoteMessages.length) return remoteMessages;
22
+
20
23
  if (type === "summary" || event.summary) {
21
24
  const content = firstString(event.summary, message.summary);
22
25
  return content ? [supplementaryMessage(provider, "Claude summary", content, timestamp, "summary")] : [];
@@ -34,9 +37,12 @@ function assistantMessages(event, message, provider, context, timestamp, content
34
37
  const text = visibleText(content);
35
38
  const thinking = thinkingText(content);
36
39
  const toolCalls = toolUseParts(content).map((part) => normalizeToolCall(part, provider)).filter(Boolean);
40
+ rememberToolCalls(context, toolCalls);
41
+ const eventMetadata = claudeEventMetadata(event);
37
42
  const metadata = {
38
43
  provider,
39
44
  eventType: firstString(event.type, event.kind),
45
+ ...eventMetadata,
40
46
  model: firstString(message.model, event.model, context.model) || undefined,
41
47
  requestId: firstString(event.requestId, event.request_id, message.id) || undefined,
42
48
  status: firstString(message.stop_reason, event.stop_reason, event.status) || undefined,
@@ -51,7 +57,8 @@ function assistantMessages(event, message, provider, context, timestamp, content
51
57
 
52
58
  function userMessages(event, message, provider, context, timestamp, content) {
53
59
  const text = visibleText(content);
54
- const toolResults = toolResultParts(content).map((part) => normalizeToolResult(part, provider, event.toolUseResult)).filter(Boolean);
60
+ const eventMetadata = claudeEventMetadata(event);
61
+ const toolResults = toolResultParts(content).map((part) => normalizeToolResult(part, provider, event, context)).filter(Boolean);
55
62
  const result = [];
56
63
  if (text) {
57
64
  result.push({
@@ -61,6 +68,7 @@ function userMessages(event, message, provider, context, timestamp, content) {
61
68
  metadata: {
62
69
  provider,
63
70
  eventType: firstString(event.type, event.kind),
71
+ ...eventMetadata,
64
72
  model: firstString(message.model, event.model, context.model) || undefined
65
73
  }
66
74
  });
@@ -74,6 +82,7 @@ function userMessages(event, message, provider, context, timestamp, content) {
74
82
  metadata: {
75
83
  provider,
76
84
  eventType: "claude-tool-result",
85
+ ...eventMetadata,
77
86
  toolResult
78
87
  }
79
88
  });
@@ -81,6 +90,49 @@ function userMessages(event, message, provider, context, timestamp, content) {
81
90
  return result;
82
91
  }
83
92
 
93
+ function claudeEventMetadata(event) {
94
+ return compactMetadata({
95
+ uuid: firstString(event.uuid),
96
+ parentUuid: firstString(event.parentUuid, event.parent_uuid),
97
+ logicalParentUuid: firstString(event.logicalParentUuid, event.logical_parent_uuid),
98
+ leafUuid: firstString(event.leafUuid, event.leaf_uuid),
99
+ promptId: firstString(event.promptId, event.prompt_id),
100
+ sourceToolAssistantUUID: firstString(event.sourceToolAssistantUUID, event.source_tool_assistant_uuid),
101
+ sourceToolUseID: firstString(event.sourceToolUseID, event.source_tool_use_id),
102
+ parentToolUseID: firstString(event.parentToolUseID, event.parent_tool_use_id),
103
+ toolUseID: firstString(event.toolUseID, event.tool_use_id),
104
+ isSidechain: typeof event.isSidechain === "boolean" ? event.isSidechain : undefined,
105
+ isVisibleInTranscriptOnly: typeof event.isVisibleInTranscriptOnly === "boolean" ? event.isVisibleInTranscriptOnly : undefined,
106
+ isCompactSummary: typeof event.isCompactSummary === "boolean" ? event.isCompactSummary : undefined,
107
+ userType: firstString(event.userType, event.user_type),
108
+ entrypoint: firstString(event.entrypoint),
109
+ claudeCodeVersion: firstString(event.version),
110
+ gitBranch: firstString(event.gitBranch, event.git_branch),
111
+ permissionMode: firstString(event.permissionMode, event.permission_mode),
112
+ agentId: firstString(event.agentId, event.agent_id),
113
+ slug: firstString(event.slug),
114
+ attributionSkill: firstString(event.attributionSkill, event.attribution_skill),
115
+ origin: firstString(event.origin),
116
+ mcpMeta: event.mcpMeta && typeof event.mcpMeta === "object" ? event.mcpMeta : undefined,
117
+ compactMetadata: event.compactMetadata && typeof event.compactMetadata === "object" ? event.compactMetadata : undefined,
118
+ apiError: claudeApiErrorMetadata(event) || undefined
119
+ }) || {};
120
+ }
121
+
122
+ function claudeApiErrorMetadata(event) {
123
+ if (!event || typeof event !== "object") return null;
124
+ if (event.isApiErrorMessage !== true && !event.error && event.apiErrorStatus == null) return null;
125
+ return compactMetadata({
126
+ isApiErrorMessage: event.isApiErrorMessage === true ? true : undefined,
127
+ status: numberValue(event.apiErrorStatus, event.api_error_status) ?? undefined,
128
+ type: firstString(event.error) || undefined,
129
+ retryAttempt: numberValue(event.retryAttempt, event.retry_attempt) ?? undefined,
130
+ maxRetries: numberValue(event.maxRetries, event.max_retries) ?? undefined,
131
+ retryInMs: numberValue(event.retryInMs, event.retry_in_ms) ?? undefined,
132
+ cause: firstString(event.cause?.message, event.cause) || undefined
133
+ });
134
+ }
135
+
84
136
  function supplementaryMessage(provider, title, content, timestamp, summaryKind, model = "") {
85
137
  return {
86
138
  role: "assistant",
@@ -96,6 +148,209 @@ function supplementaryMessage(provider, title, content, timestamp, summaryKind,
96
148
  };
97
149
  }
98
150
 
151
+ function remoteControlLifecycleMessages(event, provider, context, type, timestamp) {
152
+ if (!isClaudeRemoteControlSession(context)) return [];
153
+ if (type === "user" || type === "assistant" || type === "summary") return [];
154
+ if (type === "queue-operation") return [remoteQueueMessage(event, provider, timestamp)];
155
+ if (type === "attachment") return [remoteAttachmentMessage(event, provider, timestamp)].filter(Boolean);
156
+ if (type === "progress") return [remoteProgressMessage(event, provider, timestamp)];
157
+ if (type === "system") return [remoteSystemMessage(event, provider, timestamp)];
158
+ if (type === "permission-mode") return [remotePermissionModeMessage(event, provider, timestamp)].filter(Boolean);
159
+ if (type === "file-history-snapshot") return [remoteFileHistorySnapshotMessage(event, provider, timestamp)];
160
+ return [];
161
+ }
162
+
163
+ function isClaudeRemoteControlSession(context) {
164
+ return context?.claudeJsonl?.remoteControl === true;
165
+ }
166
+
167
+ function remoteQueueMessage(event, provider, timestamp) {
168
+ const operation = firstString(event.operation, "operation").toLowerCase();
169
+ const content = firstString(event.content);
170
+ const label = operation ? operation.replace(/_/g, " ") : "operation";
171
+ const suffix = content ? `: ${previewText(content, 240)}` : "";
172
+ return remoteContextMessage(provider, event, timestamp, "remote_control_queue", `Remote Control queue ${label}${suffix}`, {
173
+ queueOperation: operation || undefined,
174
+ queueContent: content || undefined
175
+ });
176
+ }
177
+
178
+ function remoteAttachmentMessage(event, provider, timestamp) {
179
+ const attachment = event.attachment && typeof event.attachment === "object" ? event.attachment : {};
180
+ const metadata = normalizeRemoteAttachment(attachment);
181
+ if (!metadata) return null;
182
+ return remoteContextMessage(provider, event, timestamp, remoteAttachmentContextKind(metadata.type), remoteAttachmentText(metadata), {
183
+ attachment: metadata
184
+ });
185
+ }
186
+
187
+ function remoteProgressMessage(event, provider, timestamp) {
188
+ const data = event.data && typeof event.data === "object" ? event.data : {};
189
+ const hookName = firstString(data.hookName, data.hook_name);
190
+ const hookEvent = firstString(data.hookEvent, data.hook_event);
191
+ const progressType = firstString(data.type);
192
+ const label = firstString(hookName, hookEvent, progressType, "progress");
193
+ return remoteContextMessage(provider, event, timestamp, "remote_control_progress", `Remote Control progress: ${label}`, {
194
+ progress: compactMetadata({
195
+ type: progressType || undefined,
196
+ hookEvent: hookEvent || undefined,
197
+ hookName: hookName || undefined,
198
+ command: firstString(data.command) || undefined,
199
+ parentToolUseID: firstString(event.parentToolUseID, event.parent_tool_use_id) || undefined,
200
+ toolUseID: firstString(event.toolUseID, event.tool_use_id) || undefined
201
+ })
202
+ });
203
+ }
204
+
205
+ function remoteSystemMessage(event, provider, timestamp) {
206
+ const subtype = firstString(event.subtype, event.kind, "system");
207
+ const hookCount = numberValue(event.hookCount, event.hook_count);
208
+ const hookErrors = Array.isArray(event.hookErrors) ? event.hookErrors : [];
209
+ const body = subtype === "stop_hook_summary"
210
+ ? `Remote Control stop hook summary: ${hookCount ?? 0} ${plural("hook", hookCount ?? 0)}${hookErrors.length ? `, ${hookErrors.length} ${plural("error", hookErrors.length)}` : ""}`
211
+ : `Remote Control system event: ${subtype}`;
212
+ return remoteContextMessage(provider, event, timestamp, "remote_control_system", body, {
213
+ system: compactMetadata({
214
+ subtype,
215
+ hookCount: hookCount ?? undefined,
216
+ hookInfos: Array.isArray(event.hookInfos) ? event.hookInfos.map((hook) => compactMetadata({ command: firstString(hook?.command) || undefined })).filter(Boolean) : undefined,
217
+ hookErrors: hookErrors.length ? hookErrors : undefined,
218
+ preventedContinuation: typeof event.preventedContinuation === "boolean" ? event.preventedContinuation : undefined,
219
+ stopReason: firstString(event.stopReason, event.stop_reason) || undefined,
220
+ hasOutput: typeof event.hasOutput === "boolean" ? event.hasOutput : undefined,
221
+ level: firstString(event.level) || undefined
222
+ })
223
+ });
224
+ }
225
+
226
+ function remotePermissionModeMessage(event, provider, timestamp) {
227
+ if (!timestamp) return null;
228
+ const permissionMode = firstString(event.permissionMode, event.permission_mode, "unknown");
229
+ return remoteContextMessage(provider, event, timestamp, "remote_control_permission", `Remote Control permission mode: ${permissionMode}`, {
230
+ permissionMode
231
+ });
232
+ }
233
+
234
+ function remoteFileHistorySnapshotMessage(event, provider, timestamp) {
235
+ const snapshot = event.snapshot && typeof event.snapshot === "object" ? event.snapshot : {};
236
+ const snapshotTimestamp = timestamp || eventTimestamp(snapshot);
237
+ const backups = snapshot.trackedFileBackups && typeof snapshot.trackedFileBackups === "object" ? snapshot.trackedFileBackups : {};
238
+ const paths = Object.keys(backups).sort();
239
+ const action = event.isSnapshotUpdate ? "updated" : "recorded";
240
+ const target = paths.length ? `: ${namesPreview(paths)}` : "";
241
+ return remoteContextMessage(provider, event, snapshotTimestamp, "remote_control_file_history", `Remote Control file history snapshot ${action}${target}`, {
242
+ fileHistorySnapshot: compactMetadata({
243
+ messageId: firstString(event.messageId, event.message_id, snapshot.messageId, snapshot.message_id) || undefined,
244
+ isSnapshotUpdate: typeof event.isSnapshotUpdate === "boolean" ? event.isSnapshotUpdate : undefined,
245
+ timestamp: snapshotTimestamp || undefined,
246
+ backupFileCount: paths.length || undefined,
247
+ paths: paths.length ? paths : undefined
248
+ })
249
+ });
250
+ }
251
+
252
+ function remoteContextMessage(provider, event, timestamp, contextKind, content, extraMetadata = {}) {
253
+ return {
254
+ role: "system",
255
+ content,
256
+ timestamp,
257
+ metadata: compactMetadata({
258
+ provider,
259
+ eventType: firstString(event.type, event.kind),
260
+ ...claudeEventMetadata(event),
261
+ providerGenerated: true,
262
+ contextKind,
263
+ contextSource: "claude-remote-control",
264
+ ...extraMetadata
265
+ })
266
+ };
267
+ }
268
+
269
+ function normalizeRemoteAttachment(attachment) {
270
+ const type = firstString(attachment.type);
271
+ if (!type) return null;
272
+ const content = attachment.content && typeof attachment.content === "object" ? attachment.content : {};
273
+ const text = firstString(content.content, attachment.content);
274
+ return compactMetadata({
275
+ type,
276
+ addedNames: asStringArray(attachment.addedNames),
277
+ removedNames: asStringArray(attachment.removedNames),
278
+ path: firstString(attachment.path, content.path) || undefined,
279
+ displayPath: firstString(attachment.displayPath, attachment.display_path) || undefined,
280
+ prompt: previewText(firstString(attachment.prompt), 500) || undefined,
281
+ commandMode: firstString(attachment.commandMode, attachment.command_mode) || undefined,
282
+ allowedTools: asStringArray(attachment.allowedTools),
283
+ newDate: firstString(attachment.newDate, attachment.new_date) || undefined,
284
+ itemCount: numberValue(attachment.itemCount, attachment.item_count) ?? undefined,
285
+ maxTurns: numberValue(attachment.maxTurns, attachment.max_turns) ?? undefined,
286
+ turnCount: numberValue(attachment.turnCount, attachment.turn_count) ?? undefined,
287
+ contentType: firstString(content.type) || undefined,
288
+ contentPath: firstString(content.path) || undefined,
289
+ contentDiffersFromDisk: typeof content.contentDiffersFromDisk === "boolean" ? content.contentDiffersFromDisk : undefined,
290
+ contentPreview: text ? previewText(text, 240) : undefined
291
+ });
292
+ }
293
+
294
+ function remoteAttachmentContextKind(type) {
295
+ return `remote_control_${String(type || "attachment").replace(/[^a-z0-9]+/gi, "_").replace(/^_+|_+$/g, "").toLowerCase() || "attachment"}`;
296
+ }
297
+
298
+ function remoteAttachmentText(metadata) {
299
+ const type = metadata.type;
300
+ if (type === "deferred_tools_delta") {
301
+ const names = [...(metadata.addedNames || []), ...(metadata.removedNames || [])];
302
+ return names.length ? `Remote Control tools updated: ${namesPreview(names)}` : "Remote Control tools updated";
303
+ }
304
+ if (type === "mcp_instructions_delta") {
305
+ return metadata.addedNames?.length ? `Remote Control MCP instructions updated: ${namesPreview(metadata.addedNames)}` : "Remote Control MCP instructions updated";
306
+ }
307
+ if (type === "skill_listing") {
308
+ return metadata.addedNames?.length ? `Remote Control skills available: ${namesPreview(metadata.addedNames)}` : "Remote Control skills updated";
309
+ }
310
+ if (type === "nested_memory") {
311
+ return `Remote Control memory loaded: ${firstString(metadata.displayPath, metadata.path, metadata.contentPath, "memory")}`;
312
+ }
313
+ if (type === "queued_command") {
314
+ return metadata.prompt ? `Remote Control queued command: ${metadata.prompt}` : "Remote Control queued command";
315
+ }
316
+ if (type === "command_permissions") {
317
+ return metadata.allowedTools?.length ? `Remote Control command permissions: ${namesPreview(metadata.allowedTools)}` : "Remote Control command permissions updated";
318
+ }
319
+ if (type === "edited_text_file") {
320
+ return `Remote Control edited text file: ${firstString(metadata.displayPath, metadata.path, metadata.contentPath, "file")}`;
321
+ }
322
+ if (type === "date_change") {
323
+ return metadata.newDate ? `Remote Control date changed: ${metadata.newDate}` : "Remote Control date changed";
324
+ }
325
+ if (type === "max_turns_reached") {
326
+ return `Remote Control max turns reached: ${metadata.turnCount ?? 0}/${metadata.maxTurns ?? 0}`;
327
+ }
328
+ if (type === "compact_file_reference") {
329
+ return `Remote Control compact file reference: ${firstString(metadata.displayPath, metadata.path, "file")}`;
330
+ }
331
+ if (type === "task_reminder" || type === "todo_reminder") {
332
+ return `Remote Control ${type.replace(/_/g, " ")}: ${metadata.itemCount ?? 0} ${plural("item", metadata.itemCount ?? 0)}`;
333
+ }
334
+ return `Remote Control attachment: ${type.replace(/_/g, " ")}`;
335
+ }
336
+
337
+ function namesPreview(names, limit = 8) {
338
+ const values = asStringArray(names);
339
+ const visible = values.slice(0, limit).join(", ");
340
+ const remaining = values.length - limit;
341
+ return remaining > 0 ? `${visible}, +${remaining} more` : visible;
342
+ }
343
+
344
+ function previewText(value, max = 240) {
345
+ const text = String(value || "").replace(/\s+/g, " ").trim();
346
+ if (!text) return "";
347
+ return text.length > max ? `${text.slice(0, max - 3).trimEnd()}...` : text;
348
+ }
349
+
350
+ function plural(word, count) {
351
+ return count === 1 ? word : `${word}s`;
352
+ }
353
+
99
354
  function toolUseParts(content) {
100
355
  return arrayContent(content).filter((part) => {
101
356
  const type = String(part?.type || part?.kind || "").toLowerCase();
@@ -137,6 +392,14 @@ function thinkingText(content) {
137
392
  .trim();
138
393
  }
139
394
 
395
+ function rememberToolCalls(context, toolCalls) {
396
+ if (!context || !toolCalls.length) return;
397
+ if (!context.claudeToolNamesById) context.claudeToolNamesById = new Map();
398
+ for (const call of toolCalls) {
399
+ if (call.id && call.name) context.claudeToolNamesById.set(call.id, call.name);
400
+ }
401
+ }
402
+
140
403
  function normalizeToolCall(part, provider) {
141
404
  const rawName = firstString(part.name, part.tool_name, part.toolName, part.function?.name, "tool");
142
405
  const rawArgs = parseArgs(part.input ?? part.arguments ?? part.args ?? part.function?.arguments);
@@ -170,14 +433,16 @@ function normalizeToolCall(part, provider) {
170
433
  };
171
434
  }
172
435
 
173
- function normalizeToolResult(part, provider, eventToolUseResult) {
174
- const output = extractToolResultOutput(part, eventToolUseResult);
436
+ function normalizeToolResult(part, provider, event, context = {}) {
437
+ const output = extractToolResultOutput(part, event?.toolUseResult);
175
438
  if (!output) return null;
176
- const name = firstString(part.name, part.tool_name, part.toolName, part.tool_use_id, "Tool output");
439
+ const id = firstString(part.tool_use_id, part.id, part.call_id, part.callId);
440
+ const name = firstString(part.name, part.tool_name, part.toolName, id ? context.claudeToolNamesById?.get(id) : "", "Tool output");
177
441
  const category = toolCategory(name, part.type);
178
- return {
442
+ return compactMetadata({
179
443
  provider,
180
- id: firstString(part.tool_use_id, part.id, part.call_id, part.callId) || undefined,
444
+ id: id || undefined,
445
+ name,
181
446
  kind: displayName(name),
182
447
  title: part.is_error ? "Tool error" : "Tool result",
183
448
  rawCategory: firstString(part.type, part.kind) || undefined,
@@ -187,8 +452,10 @@ function normalizeToolResult(part, provider, eventToolUseResult) {
187
452
  output,
188
453
  lineCount: output.split("\n").length,
189
454
  collapsed: output.split("\n").length > 18,
190
- status: part.is_error ? "error" : "completed"
191
- };
455
+ status: part.is_error ? "error" : "completed",
456
+ sourceToolUseID: firstString(event?.sourceToolUseID, event?.source_tool_use_id) || undefined,
457
+ structuredContent: event?.mcpMeta?.structuredContent && typeof event.mcpMeta.structuredContent === "object" ? event.mcpMeta.structuredContent : undefined
458
+ });
192
459
  }
193
460
 
194
461
  function extractToolResultOutput(part, eventToolUseResult) {
@@ -204,13 +471,27 @@ function claudeUsage(usage) {
204
471
  const cacheCreation = numberValue(usage.cache_creation_input_tokens, usage.cacheCreationInputTokens);
205
472
  const cacheRead = numberValue(usage.cache_read_input_tokens, usage.cacheReadInputTokens);
206
473
  if ([input, output, cacheCreation, cacheRead].every((value) => value == null)) return null;
207
- return {
474
+ const serverToolUse = compactMetadata({
475
+ webSearchRequests: numberValue(usage.server_tool_use?.web_search_requests, usage.serverToolUse?.webSearchRequests),
476
+ webFetchRequests: numberValue(usage.server_tool_use?.web_fetch_requests, usage.serverToolUse?.webFetchRequests)
477
+ });
478
+ const cacheCreationDetails = compactMetadata({
479
+ ephemeral1hInputTokens: numberValue(usage.cache_creation?.ephemeral_1h_input_tokens, usage.cacheCreation?.ephemeral1hInputTokens),
480
+ ephemeral5mInputTokens: numberValue(usage.cache_creation?.ephemeral_5m_input_tokens, usage.cacheCreation?.ephemeral5mInputTokens)
481
+ });
482
+ return compactMetadata({
208
483
  inputTokens: input ?? undefined,
209
484
  outputTokens: output ?? undefined,
210
485
  cacheCreationInputTokens: cacheCreation ?? undefined,
211
486
  cacheReadInputTokens: cacheRead ?? undefined,
212
- totalTokens: [input, output, cacheCreation, cacheRead].reduce((sum, value) => sum + (value || 0), 0)
213
- };
487
+ totalTokens: [input, output, cacheCreation, cacheRead].reduce((sum, value) => sum + (value || 0), 0),
488
+ serviceTier: firstString(usage.service_tier, usage.serviceTier) || undefined,
489
+ speed: firstString(usage.speed) || undefined,
490
+ inferenceGeo: firstString(usage.inference_geo, usage.inferenceGeo) || undefined,
491
+ serverToolUse: serverToolUse || undefined,
492
+ cacheCreation: cacheCreationDetails || undefined,
493
+ iterationCount: Array.isArray(usage.iterations) ? usage.iterations.length : undefined
494
+ });
214
495
  }
215
496
 
216
497
  function extractText(value, depth = 0) {
@@ -328,6 +609,11 @@ function numberValue(...values) {
328
609
  return null;
329
610
  }
330
611
 
612
+ function asStringArray(value) {
613
+ if (!Array.isArray(value)) return [];
614
+ return value.filter((item) => typeof item === "string" && item.trim()).map((item) => item.trim());
615
+ }
616
+
331
617
  function firstString(...values) {
332
618
  for (const value of values) {
333
619
  if (typeof value === "string" && value.trim()) return value.trim();
@@ -339,6 +625,18 @@ function firstLine(value) {
339
625
  return String(value || "").split(/\r?\n/).find((line) => line.trim())?.trim() || "";
340
626
  }
341
627
 
628
+ function compactMetadata(value) {
629
+ if (!value || typeof value !== "object" || Array.isArray(value)) return value || null;
630
+ const result = {};
631
+ for (const [key, item] of Object.entries(value)) {
632
+ if (item === undefined || item === null || item === "") continue;
633
+ if (Array.isArray(item) && !item.length) continue;
634
+ if (item && typeof item === "object" && !Array.isArray(item) && !Object.keys(item).length) continue;
635
+ result[key] = item;
636
+ }
637
+ return Object.keys(result).length ? result : null;
638
+ }
639
+
342
640
  function isClaudeProvider(provider) {
343
641
  return provider === "claude_code" || provider === "claude_sdk" || provider === "claude_desktop";
344
642
  }
@@ -607,6 +607,7 @@ function geminiUsage(value) {
607
607
  totalTokens,
608
608
  thoughtsTokens,
609
609
  cacheReadTokens,
610
+ cacheReadTokensIncludedInInput: cacheReadTokens ? true : undefined,
610
611
  toolUsePromptTokens
611
612
  });
612
613
  return Object.keys(normalized).length ? normalized : null;
@@ -880,7 +881,7 @@ function geminiSessionSummaryUsage(summary) {
880
881
  cacheInputTokens += numberValue(model.cacheReadTokens) || 0;
881
882
  }
882
883
  if (!inputTokens && !outputTokens && !cacheInputTokens) return null;
883
- return { inputTokens, outputTokens, cacheInputTokens, totalTokens: inputTokens + outputTokens };
884
+ return { inputTokens, outputTokens, cacheInputTokens, cacheInputTokensIncludedInInput: cacheInputTokens ? true : undefined, totalTokens: inputTokens + outputTokens };
884
885
  }
885
886
 
886
887
  function mergeGeminiSessionSummaries(summaries) {
@@ -23,6 +23,21 @@ const PROVIDER_ADAPTERS = [
23
23
  run: ({ helpers, since, options, env }) =>
24
24
  helpers.importCodexProvider("codex", since, { ...options, codexSource: "vscode" }, env)
25
25
  },
26
+ {
27
+ source: "codex-sdk",
28
+ provider: "codex",
29
+ sourceType: "codex-sdk-history",
30
+ label: "Codex SDK jobs",
31
+ run: ({ helpers, since, options, env }) =>
32
+ helpers.importCodexProvider("codex", since, { ...options, codexSource: "exec" }, env)
33
+ },
34
+ {
35
+ source: "chatgpt",
36
+ provider: "chatgpt",
37
+ label: "ChatGPT",
38
+ manual: true,
39
+ run: ({ helpers }) => helpers.manualImportInstructionResult("chatgpt")
40
+ },
26
41
  {
27
42
  source: "claude",
28
43
  provider: "claude_code",
@@ -59,6 +74,13 @@ const PROVIDER_ADAPTERS = [
59
74
  run: ({ helpers, since, options, env }) =>
60
75
  helpers.importClaudeDesktopProvider("claude_desktop", since, { ...options, claudeDesktopKind: "claude-workspace-desktop" }, env)
61
76
  },
77
+ {
78
+ source: "claude-web",
79
+ provider: "claude_web",
80
+ label: "Claude.ai",
81
+ manual: true,
82
+ run: ({ helpers }) => helpers.manualImportInstructionResult("claude-web")
83
+ },
62
84
  {
63
85
  source: "cursor",
64
86
  provider: "cursor",