agentel 0.2.0 → 0.2.2
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 +161 -63
- package/agentlog-spec.md +42 -35
- package/bin/agentlog-recall.js +2 -0
- package/bin/agentlog.js +12 -0
- package/docs/code-reference.md +120 -34
- package/docs/history-source-handling.md +236 -81
- package/docs/release.md +8 -8
- package/package.json +5 -4
- package/src/archive.js +278 -20
- package/src/cli.js +3457 -511
- package/src/config.js +42 -1
- package/src/doctor.js +167 -10
- package/src/importers/gemini.js +369 -7
- package/src/importers.js +1837 -135
- package/src/mcp.js +4 -1
- package/src/parser-versions.js +37 -22
- package/src/paths.js +4 -2
- package/src/redaction.js +140 -17
- package/src/search.js +671 -52
- package/src/supervisor.js +206 -57
- package/src/sync.js +459 -12
package/src/importers/gemini.js
CHANGED
|
@@ -5,6 +5,14 @@ const { toIso } = require("../archive");
|
|
|
5
5
|
const PROVIDER = "gemini_cli";
|
|
6
6
|
|
|
7
7
|
function parseGeminiCliJsonl(text, options = {}) {
|
|
8
|
+
return parseGeminiCliEvents(geminiJsonlEvents(text), options);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function parseGeminiCliJsonlSessions(text, options = {}) {
|
|
12
|
+
return parseGeminiCliEventSessions(geminiJsonlEvents(text), options);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function geminiJsonlEvents(text) {
|
|
8
16
|
const events = [];
|
|
9
17
|
for (const line of String(text || "").split(/\r?\n/)) {
|
|
10
18
|
if (!line.trim()) continue;
|
|
@@ -14,13 +22,29 @@ function parseGeminiCliJsonl(text, options = {}) {
|
|
|
14
22
|
// Ignore malformed partial lines in append-only history files.
|
|
15
23
|
}
|
|
16
24
|
}
|
|
17
|
-
return
|
|
25
|
+
return events;
|
|
18
26
|
}
|
|
19
27
|
|
|
20
28
|
function parseGeminiCliJson(data, options = {}) {
|
|
21
29
|
return parseGeminiCliEvents(geminiEventList(data), { ...options, root: data });
|
|
22
30
|
}
|
|
23
31
|
|
|
32
|
+
function parseGeminiCliJsonSessions(data, options = {}) {
|
|
33
|
+
return parseGeminiCliEventSessions(geminiEventList(data), { ...options, root: data });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parseGeminiCliEventSessions(events, options = {}) {
|
|
37
|
+
const groups = geminiSessionEventGroups(events, options);
|
|
38
|
+
return groups
|
|
39
|
+
.map((group) =>
|
|
40
|
+
parseGeminiCliEvents(group.events, {
|
|
41
|
+
...options,
|
|
42
|
+
sessionId: group.sessionId || options.sessionId
|
|
43
|
+
})
|
|
44
|
+
)
|
|
45
|
+
.filter((session) => session.messages.length);
|
|
46
|
+
}
|
|
47
|
+
|
|
24
48
|
function parseGeminiCliEvents(events, options = {}) {
|
|
25
49
|
const root = options.root && typeof options.root === "object" ? options.root : {};
|
|
26
50
|
const normalizedEvents = coalesceGeminiIncrementalEvents(events);
|
|
@@ -31,19 +55,27 @@ function parseGeminiCliEvents(events, options = {}) {
|
|
|
31
55
|
};
|
|
32
56
|
let sessionId = firstString(options.sessionId, geminiSessionId(root));
|
|
33
57
|
let title = firstString(options.title, geminiTitle(root));
|
|
58
|
+
let topicTitle = "";
|
|
34
59
|
let cwd = firstString(options.cwd, geminiCwd(root));
|
|
35
60
|
const messages = [];
|
|
61
|
+
const sessionSummaries = [];
|
|
36
62
|
|
|
37
63
|
for (let index = 0; index < normalizedEvents.length; index++) {
|
|
38
64
|
const event = normalizedEvents[index];
|
|
39
65
|
if (!event || typeof event !== "object") continue;
|
|
40
66
|
context.model = firstString(geminiModel(event), context.model);
|
|
41
|
-
sessionId ||=
|
|
67
|
+
sessionId ||= geminiEventSessionId(event);
|
|
42
68
|
title ||= geminiTitle(event);
|
|
43
69
|
cwd ||= geminiCwd(event);
|
|
44
70
|
const timestamp = eventTimestamp(event, fallbackTime, index);
|
|
45
71
|
context.lastTimestamp = timestamp;
|
|
46
72
|
const usage = geminiUsage(event);
|
|
73
|
+
const sessionSummary = geminiSessionSummary(event, timestamp);
|
|
74
|
+
if (sessionSummary) {
|
|
75
|
+
sessionSummaries.push(sessionSummary);
|
|
76
|
+
sessionId ||= sessionSummary.sessionId;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
47
79
|
|
|
48
80
|
const checkpoint = geminiCheckpointMessage(event, timestamp);
|
|
49
81
|
if (checkpoint) {
|
|
@@ -52,6 +84,7 @@ function parseGeminiCliEvents(events, options = {}) {
|
|
|
52
84
|
}
|
|
53
85
|
|
|
54
86
|
const extracted = geminiMessagesFromEvent(event, { ...context, timestamp, usage });
|
|
87
|
+
topicTitle = firstString(geminiTopicTitleFromMessages(extracted), topicTitle);
|
|
55
88
|
if (extracted.length) {
|
|
56
89
|
messages.push(...extracted);
|
|
57
90
|
continue;
|
|
@@ -70,12 +103,13 @@ function parseGeminiCliEvents(events, options = {}) {
|
|
|
70
103
|
|
|
71
104
|
return {
|
|
72
105
|
sessionId,
|
|
73
|
-
title,
|
|
106
|
+
title: firstString(topicTitle, title),
|
|
74
107
|
cwd,
|
|
75
108
|
model: context.model,
|
|
76
109
|
messages: sorted,
|
|
77
110
|
startedAt: sorted[0]?.timestamp || "",
|
|
78
|
-
endedAt: sorted[sorted.length - 1]?.timestamp || ""
|
|
111
|
+
endedAt: sorted[sorted.length - 1]?.timestamp || "",
|
|
112
|
+
sessionSummary: mergeGeminiSessionSummaries(sessionSummaries)
|
|
79
113
|
};
|
|
80
114
|
}
|
|
81
115
|
|
|
@@ -101,6 +135,27 @@ function geminiEventList(data) {
|
|
|
101
135
|
return [data];
|
|
102
136
|
}
|
|
103
137
|
|
|
138
|
+
function geminiSessionEventGroups(events, options = {}) {
|
|
139
|
+
const rootSessionId = firstString(options.sessionId, geminiSessionId(options.root));
|
|
140
|
+
const groups = new Map();
|
|
141
|
+
const explicitSessionIds = new Set();
|
|
142
|
+
let currentSessionId = rootSessionId;
|
|
143
|
+
for (const event of events || []) {
|
|
144
|
+
const explicitSessionId = geminiEventSessionId(event) || geminiSessionSummaryId(event);
|
|
145
|
+
if (explicitSessionId) {
|
|
146
|
+
explicitSessionIds.add(explicitSessionId);
|
|
147
|
+
currentSessionId = explicitSessionId;
|
|
148
|
+
}
|
|
149
|
+
const key = currentSessionId || "";
|
|
150
|
+
if (!groups.has(key)) groups.set(key, { sessionId: key, events: [] });
|
|
151
|
+
groups.get(key).events.push(event);
|
|
152
|
+
}
|
|
153
|
+
if (explicitSessionIds.size <= 1) {
|
|
154
|
+
return [{ sessionId: rootSessionId || [...explicitSessionIds][0] || "", events: events || [] }];
|
|
155
|
+
}
|
|
156
|
+
return [...groups.values()];
|
|
157
|
+
}
|
|
158
|
+
|
|
104
159
|
function coalesceGeminiIncrementalEvents(events) {
|
|
105
160
|
const output = [];
|
|
106
161
|
const indexes = new Map();
|
|
@@ -109,7 +164,7 @@ function coalesceGeminiIncrementalEvents(events) {
|
|
|
109
164
|
output.push(event);
|
|
110
165
|
continue;
|
|
111
166
|
}
|
|
112
|
-
const id =
|
|
167
|
+
const id = geminiIncrementalEventKey(event);
|
|
113
168
|
if (!id) {
|
|
114
169
|
output.push(event);
|
|
115
170
|
continue;
|
|
@@ -125,6 +180,13 @@ function coalesceGeminiIncrementalEvents(events) {
|
|
|
125
180
|
return output;
|
|
126
181
|
}
|
|
127
182
|
|
|
183
|
+
function geminiIncrementalEventKey(event) {
|
|
184
|
+
const id = geminiEventId(event);
|
|
185
|
+
if (!id) return "";
|
|
186
|
+
const sessionId = geminiEventSessionId(event);
|
|
187
|
+
return sessionId ? `${sessionId}:${id}` : id;
|
|
188
|
+
}
|
|
189
|
+
|
|
128
190
|
function mergeGeminiEvent(previous, next) {
|
|
129
191
|
const merged = { ...(previous && typeof previous === "object" ? previous : {}) };
|
|
130
192
|
for (const [key, value] of Object.entries(next || {})) {
|
|
@@ -571,6 +633,270 @@ function applyUsageToLastAssistant(messages, usage) {
|
|
|
571
633
|
target.metadata = { ...(target.metadata || {}), usage };
|
|
572
634
|
}
|
|
573
635
|
|
|
636
|
+
function geminiTopicTitleFromMessages(messages) {
|
|
637
|
+
let title = "";
|
|
638
|
+
for (const message of messages || []) {
|
|
639
|
+
for (const call of asArray(message?.metadata?.toolCalls)) {
|
|
640
|
+
if (!isGeminiTopicContextCall(call)) continue;
|
|
641
|
+
title = firstString(geminiTopicTitleFromArgs(call.arguments), geminiTopicTitleFromArgs(call.rawInputSummary), title);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return title;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function isGeminiTopicContextCall(call) {
|
|
648
|
+
return [call?.name, call?.displayName, call?.display_name, call?.title]
|
|
649
|
+
.filter((value) => typeof value === "string" && value.trim())
|
|
650
|
+
.some((name) => {
|
|
651
|
+
const normalized = name.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
652
|
+
return normalized === "updatetopic" || normalized === "updatetopiccontext" || /update.*topic.*context/i.test(name);
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function geminiTopicTitleFromArgs(args) {
|
|
657
|
+
if (!args) return "";
|
|
658
|
+
if (typeof args === "string") {
|
|
659
|
+
const parsed = parseToolArgsValue(args);
|
|
660
|
+
if (parsed && typeof parsed === "object") return geminiTopicTitleFromArgs(parsed);
|
|
661
|
+
return cleanGeminiTopicTitle(
|
|
662
|
+
firstString(
|
|
663
|
+
matchGeminiTopicTitle(args, /\btitle\s*:\s*([^,\n]+)/i),
|
|
664
|
+
matchGeminiTopicTitle(args, /\btitle\s+(.+?)(?:,\s*(?:strategic[_\s-]*intent|summary)\b|\s+and\s+title\s*:|$)/i)
|
|
665
|
+
)
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
if (typeof args !== "object") return "";
|
|
669
|
+
return cleanGeminiTopicTitle(
|
|
670
|
+
firstString(
|
|
671
|
+
args.title,
|
|
672
|
+
args.topicTitle,
|
|
673
|
+
args.topic_title,
|
|
674
|
+
args.topic,
|
|
675
|
+
args.topic?.title,
|
|
676
|
+
args.context?.title,
|
|
677
|
+
args.topicContext?.title,
|
|
678
|
+
args.topic_context?.title
|
|
679
|
+
)
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function matchGeminiTopicTitle(text, pattern) {
|
|
684
|
+
const match = String(text || "").match(pattern);
|
|
685
|
+
return match ? match[1] : "";
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function cleanGeminiTopicTitle(value) {
|
|
689
|
+
const title = String(value || "")
|
|
690
|
+
.replace(/\s+/g, " ")
|
|
691
|
+
.trim()
|
|
692
|
+
.replace(/^["'`]+|["'`]+$/g, "")
|
|
693
|
+
.trim();
|
|
694
|
+
if (!title || /^update topic context$/i.test(title)) return "";
|
|
695
|
+
return title;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function geminiSessionSummary(event, timestamp) {
|
|
699
|
+
const text = geminiSessionSummaryText(event);
|
|
700
|
+
const parsed = parseGeminiSessionSummaryText(text, timestamp);
|
|
701
|
+
if (parsed) return parsed;
|
|
702
|
+
const value = event?.sessionSummary || event?.session_summary || event?.summary;
|
|
703
|
+
if (!value || typeof value !== "object") return null;
|
|
704
|
+
const structured = compactMetadata({
|
|
705
|
+
sessionId: firstString(value.sessionId, value.session_id, value.id),
|
|
706
|
+
resumeCommand: firstString(value.resumeCommand, value.resume_command),
|
|
707
|
+
toolCalls: value.toolCalls || value.tool_calls,
|
|
708
|
+
successRatePercent: numberValue(value.successRatePercent ?? value.success_rate_percent ?? value.successRate ?? value.success_rate),
|
|
709
|
+
userAgreement: value.userAgreement || value.user_agreement,
|
|
710
|
+
performance: value.performance,
|
|
711
|
+
modelUsage: Array.isArray(value.modelUsage) ? value.modelUsage : Array.isArray(value.model_usage) ? value.model_usage : undefined,
|
|
712
|
+
occurredAt: timestamp
|
|
713
|
+
});
|
|
714
|
+
const usage = geminiSessionSummaryUsage(structured);
|
|
715
|
+
if (usage) structured.usage = usage;
|
|
716
|
+
return Object.keys(structured).length ? structured : null;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function geminiSessionSummaryText(event) {
|
|
720
|
+
const candidates = [
|
|
721
|
+
event?.sessionSummary,
|
|
722
|
+
event?.session_summary,
|
|
723
|
+
event?.summary,
|
|
724
|
+
event?.content,
|
|
725
|
+
event?.message,
|
|
726
|
+
event?.text,
|
|
727
|
+
event?.output,
|
|
728
|
+
event?.payload?.sessionSummary,
|
|
729
|
+
event?.payload?.session_summary,
|
|
730
|
+
event?.payload?.summary,
|
|
731
|
+
event?.payload?.content,
|
|
732
|
+
event?.payload?.message,
|
|
733
|
+
event?.payload?.text,
|
|
734
|
+
event?.data?.summary,
|
|
735
|
+
event?.data?.content,
|
|
736
|
+
event?.data?.message,
|
|
737
|
+
event?.data?.text
|
|
738
|
+
];
|
|
739
|
+
for (const candidate of candidates) {
|
|
740
|
+
if (typeof candidate === "string" && looksLikeGeminiSessionSummary(candidate)) return candidate;
|
|
741
|
+
}
|
|
742
|
+
return "";
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function geminiSessionSummaryId(event) {
|
|
746
|
+
const text = geminiSessionSummaryText(event);
|
|
747
|
+
if (!text) return "";
|
|
748
|
+
return parseGeminiSessionSummaryText(text)?.sessionId || "";
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function looksLikeGeminiSessionSummary(text) {
|
|
752
|
+
const normalized = String(text || "");
|
|
753
|
+
return /Interaction Summary/i.test(normalized) && /(?:Session ID:|To resume this session:)/i.test(normalized);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function parseGeminiSessionSummaryText(text, timestamp = "") {
|
|
757
|
+
if (!looksLikeGeminiSessionSummary(text)) return null;
|
|
758
|
+
const lines = cleanGeminiSessionSummaryLines(text);
|
|
759
|
+
const summary = { occurredAt: timestamp || undefined };
|
|
760
|
+
const modelUsage = [];
|
|
761
|
+
let inModelTable = false;
|
|
762
|
+
let lastModel = null;
|
|
763
|
+
for (const line of lines) {
|
|
764
|
+
const sessionId = line.match(/^Session ID:\s*(.+)$/i);
|
|
765
|
+
if (sessionId) {
|
|
766
|
+
summary.sessionId = sessionId[1].trim();
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
const toolCalls = line.match(/^Tool Calls:\s*([\d,]+)(?:\s*\(\s*(?:[\u2713\u2714]\s*)?([\d,]+)?\s*(?:x|\u00d7)\s*([\d,]+)\s*\))?/i);
|
|
770
|
+
if (toolCalls) {
|
|
771
|
+
const total = numberValue(toolCalls[1]);
|
|
772
|
+
const succeeded = numberValue(toolCalls[2]);
|
|
773
|
+
const failed = numberValue(toolCalls[3]);
|
|
774
|
+
summary.toolCalls = compactMetadata({
|
|
775
|
+
total,
|
|
776
|
+
succeeded: succeeded || (Number.isFinite(total) && Number.isFinite(failed) ? total - failed : undefined),
|
|
777
|
+
failed
|
|
778
|
+
});
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
const success = line.match(/^Success Rate:\s*([\d.]+)%/i);
|
|
782
|
+
if (success) {
|
|
783
|
+
summary.successRatePercent = numberValue(success[1]);
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
const agreement = line.match(/^User Agreement:\s*([\d.]+)%(?:\s*\(([\d,]+)\s+reviewed\))?/i);
|
|
787
|
+
if (agreement) {
|
|
788
|
+
summary.userAgreement = compactMetadata({
|
|
789
|
+
ratePercent: numberValue(agreement[1]),
|
|
790
|
+
reviewed: numberValue(agreement[2])
|
|
791
|
+
});
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
const wall = line.match(/^Wall Time:\s*(.+)$/i);
|
|
795
|
+
if (wall) {
|
|
796
|
+
summary.performance = { ...(summary.performance || {}), wallTimeMs: parseGeminiDurationMs(wall[1]) };
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
const active = line.match(/^Agent Active:\s*(.+)$/i);
|
|
800
|
+
if (active) {
|
|
801
|
+
summary.performance = { ...(summary.performance || {}), agentActiveMs: parseGeminiDurationMs(active[1]) };
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
const api = line.match(/^(?:\u00bb\s*)?API Time:\s*([^(]+?)(?:\s*\(([\d.]+)%\))?$/i);
|
|
805
|
+
if (api) {
|
|
806
|
+
summary.performance = { ...(summary.performance || {}), apiTimeMs: parseGeminiDurationMs(api[1]), apiTimePercent: numberValue(api[2]) };
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
const tool = line.match(/^(?:\u00bb\s*)?Tool Time:\s*([^(]+?)(?:\s*\(([\d.]+)%\))?$/i);
|
|
810
|
+
if (tool) {
|
|
811
|
+
summary.performance = { ...(summary.performance || {}), toolTimeMs: parseGeminiDurationMs(tool[1]), toolTimePercent: numberValue(tool[2]) };
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
const resume = line.match(/^To resume this session:\s*(.+)$/i);
|
|
815
|
+
if (resume) {
|
|
816
|
+
summary.resumeCommand = resume[1].trim();
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
if (/^Model\s+Reqs\s+Input Tokens\s+Cache Reads\s+Output Tokens$/i.test(line)) {
|
|
820
|
+
inModelTable = true;
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
if (!inModelTable) continue;
|
|
824
|
+
if (/^Use \/model\b/i.test(line) || /^Model Usage$/i.test(line)) continue;
|
|
825
|
+
const model = parseGeminiModelUsageLine(line);
|
|
826
|
+
if (!model) continue;
|
|
827
|
+
if (model.role && lastModel) {
|
|
828
|
+
lastModel.roles = (lastModel.roles || []).concat(model);
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
modelUsage.push(model);
|
|
832
|
+
lastModel = model;
|
|
833
|
+
}
|
|
834
|
+
if (modelUsage.length) summary.modelUsage = modelUsage;
|
|
835
|
+
const usage = geminiSessionSummaryUsage(summary);
|
|
836
|
+
if (usage) summary.usage = usage;
|
|
837
|
+
const compact = compactMetadata(summary);
|
|
838
|
+
if (compact.performance) compact.performance = compactMetadata(compact.performance);
|
|
839
|
+
if (compact.toolCalls) compact.toolCalls = compactMetadata(compact.toolCalls);
|
|
840
|
+
if (compact.userAgreement) compact.userAgreement = compactMetadata(compact.userAgreement);
|
|
841
|
+
return Object.keys(compact).length ? compact : null;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function cleanGeminiSessionSummaryLines(text) {
|
|
845
|
+
return String(text || "")
|
|
846
|
+
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
|
|
847
|
+
.split(/\r?\n/)
|
|
848
|
+
.map((line) =>
|
|
849
|
+
line
|
|
850
|
+
.replace(/^[\s\u2502\u2503\u2551]+|[\s\u2502\u2503\u2551]+$/g, "")
|
|
851
|
+
.replace(/^[\u00bb]\s*/, "\u00bb ")
|
|
852
|
+
.trim()
|
|
853
|
+
)
|
|
854
|
+
.filter((line) => line && /[A-Za-z0-9/]/.test(line) && !/^[\u2500-\u257f\s-]+$/.test(line));
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function parseGeminiModelUsageLine(line) {
|
|
858
|
+
const role = /^\u21b3\s*/.test(line);
|
|
859
|
+
const normalized = line.replace(/^\u21b3\s*/, "");
|
|
860
|
+
const match = normalized.match(/^(.+?)\s+([\d,]+)\s+([\d,]+)\s+([\d,]+)\s+([\d,]+)$/);
|
|
861
|
+
if (!match) return null;
|
|
862
|
+
const row = {
|
|
863
|
+
requests: numberValue(match[2]),
|
|
864
|
+
inputTokens: numberValue(match[3]),
|
|
865
|
+
cacheReadTokens: numberValue(match[4]),
|
|
866
|
+
outputTokens: numberValue(match[5])
|
|
867
|
+
};
|
|
868
|
+
if (role) return { role: match[1].trim(), ...row };
|
|
869
|
+
return { model: match[1].trim(), ...row };
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function geminiSessionSummaryUsage(summary) {
|
|
873
|
+
const models = Array.isArray(summary?.modelUsage) ? summary.modelUsage : [];
|
|
874
|
+
let inputTokens = 0;
|
|
875
|
+
let outputTokens = 0;
|
|
876
|
+
let cacheInputTokens = 0;
|
|
877
|
+
for (const model of models) {
|
|
878
|
+
inputTokens += numberValue(model.inputTokens) || 0;
|
|
879
|
+
outputTokens += numberValue(model.outputTokens) || 0;
|
|
880
|
+
cacheInputTokens += numberValue(model.cacheReadTokens) || 0;
|
|
881
|
+
}
|
|
882
|
+
if (!inputTokens && !outputTokens && !cacheInputTokens) return null;
|
|
883
|
+
return { inputTokens, outputTokens, cacheInputTokens, totalTokens: inputTokens + outputTokens };
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function mergeGeminiSessionSummaries(summaries) {
|
|
887
|
+
const values = summaries.filter(Boolean);
|
|
888
|
+
if (!values.length) return null;
|
|
889
|
+
return values.reduce((merged, summary) => ({
|
|
890
|
+
...merged,
|
|
891
|
+
...summary,
|
|
892
|
+
toolCalls: { ...(merged.toolCalls || {}), ...(summary.toolCalls || {}) },
|
|
893
|
+
userAgreement: { ...(merged.userAgreement || {}), ...(summary.userAgreement || {}) },
|
|
894
|
+
performance: { ...(merged.performance || {}), ...(summary.performance || {}) },
|
|
895
|
+
modelUsage: summary.modelUsage || merged.modelUsage,
|
|
896
|
+
usage: summary.usage || merged.usage
|
|
897
|
+
}), {});
|
|
898
|
+
}
|
|
899
|
+
|
|
574
900
|
function geminiSessionId(value) {
|
|
575
901
|
return firstString(
|
|
576
902
|
value?.sessionId,
|
|
@@ -586,6 +912,20 @@ function geminiSessionId(value) {
|
|
|
586
912
|
);
|
|
587
913
|
}
|
|
588
914
|
|
|
915
|
+
function geminiEventSessionId(value) {
|
|
916
|
+
return firstString(
|
|
917
|
+
value?.sessionId,
|
|
918
|
+
value?.session_id,
|
|
919
|
+
value?.conversationId,
|
|
920
|
+
value?.conversation_id,
|
|
921
|
+
value?.chatId,
|
|
922
|
+
value?.chat_id,
|
|
923
|
+
value?.session?.id,
|
|
924
|
+
value?.metadata?.sessionId,
|
|
925
|
+
value?.metadata?.session_id
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
|
|
589
929
|
function geminiTitle(value) {
|
|
590
930
|
return firstString(value?.title, value?.name, value?.summary, value?.metadata?.title, value?.session?.title);
|
|
591
931
|
}
|
|
@@ -758,10 +1098,29 @@ function firstString(...values) {
|
|
|
758
1098
|
}
|
|
759
1099
|
|
|
760
1100
|
function numberValue(value) {
|
|
761
|
-
|
|
1101
|
+
if (value === undefined || value === null || value === "") return undefined;
|
|
1102
|
+
const number = Number(String(value ?? "").replace(/,/g, ""));
|
|
762
1103
|
return Number.isFinite(number) ? number : undefined;
|
|
763
1104
|
}
|
|
764
1105
|
|
|
1106
|
+
function parseGeminiDurationMs(value) {
|
|
1107
|
+
const text = String(value || "").trim();
|
|
1108
|
+
if (!text) return undefined;
|
|
1109
|
+
let total = 0;
|
|
1110
|
+
const re = /([\d.]+)\s*(ms|milliseconds?|h|hr|hrs|hours?|m|min|mins|minutes?|s|sec|secs|seconds?)/gi;
|
|
1111
|
+
let match;
|
|
1112
|
+
while ((match = re.exec(text))) {
|
|
1113
|
+
const amount = Number(match[1]);
|
|
1114
|
+
if (!Number.isFinite(amount)) continue;
|
|
1115
|
+
const unit = match[2].toLowerCase();
|
|
1116
|
+
if (unit.startsWith("ms") || unit.startsWith("millisecond")) total += amount;
|
|
1117
|
+
else if (unit === "h" || unit.startsWith("hr") || unit.startsWith("hour")) total += amount * 60 * 60 * 1000;
|
|
1118
|
+
else if (unit === "m" || unit.startsWith("min") || unit.startsWith("minute")) total += amount * 60 * 1000;
|
|
1119
|
+
else total += amount * 1000;
|
|
1120
|
+
}
|
|
1121
|
+
return total ? Math.round(total) : undefined;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
765
1124
|
function asArray(value) {
|
|
766
1125
|
if (Array.isArray(value)) return value;
|
|
767
1126
|
return value == null ? [] : [value];
|
|
@@ -791,5 +1150,8 @@ function uniqueObjects(values) {
|
|
|
791
1150
|
module.exports = {
|
|
792
1151
|
parseGeminiCliEvents,
|
|
793
1152
|
parseGeminiCliJson,
|
|
794
|
-
|
|
1153
|
+
parseGeminiCliJsonSessions,
|
|
1154
|
+
parseGeminiCliJsonl,
|
|
1155
|
+
parseGeminiCliJsonlSessions,
|
|
1156
|
+
parseGeminiSessionSummaryText
|
|
795
1157
|
};
|