codesesh 1.0.2 → 1.0.4
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 +18 -10
- package/dist/chunk-3SXCTDD6.js +326 -0
- package/dist/chunk-3SXCTDD6.js.map +1 -0
- package/dist/chunk-BYB77UAJ.js +6054 -0
- package/dist/chunk-BYB77UAJ.js.map +1 -0
- package/dist/{chunk-PROMURPM.js → chunk-G4HRD3AR.js} +13 -8
- package/dist/chunk-G4HRD3AR.js.map +1 -0
- package/dist/chunk-MBZ4VV4C.js +670 -0
- package/dist/chunk-MBZ4VV4C.js.map +1 -0
- package/dist/chunk-QH2HOXJH.js +2 -0
- package/dist/chunk-RF3EEWPH.js +17 -0
- package/dist/chunk-RF3EEWPH.js.map +1 -0
- package/dist/chunk-W4PZHL2C.js +2035 -0
- package/dist/chunk-W4PZHL2C.js.map +1 -0
- package/dist/chunk-YCUN52HA.js +8422 -0
- package/dist/chunk-YCUN52HA.js.map +1 -0
- package/dist/discovery-K3GLISI6.js +102 -0
- package/dist/discovery-K3GLISI6.js.map +1 -0
- package/dist/index.js +3016 -1392
- package/dist/index.js.map +1 -1
- package/dist/project-identity-worker.js +29 -0
- package/dist/project-identity-worker.js.map +1 -0
- package/dist/scan-refresh-worker.js +129 -139
- package/dist/scan-refresh-worker.js.map +1 -1
- package/dist/search-index-worker.js +39 -13
- package/dist/search-index-worker.js.map +1 -1
- package/dist/smart-tag-worker.js +8 -5
- package/dist/smart-tag-worker.js.map +1 -1
- package/dist/web/assets/InteractiveReceipt-Dk50TBsH.js +1 -0
- package/dist/web/assets/OverviewScreen-5hvQVp4H.js +1 -0
- package/dist/web/assets/{PrismHighlighter-BZkkX0AU.js → PrismHighlighter-B1a2dPJO.js} +2 -2
- package/dist/web/assets/Projects-Cy_v2yep.js +1 -0
- package/dist/web/assets/SearchResultsPanel-DfheEwpz.js +1 -0
- package/dist/web/assets/SessionDetail-DRI8R3st.js +53 -0
- package/dist/web/assets/api-GP9UCedC.js +1 -0
- package/dist/web/assets/contract-xJyW5u7a.js +2 -0
- package/dist/web/assets/index-Bd-YkWkZ.css +1 -0
- package/dist/web/assets/index-DEjvzumm.js +1410 -0
- package/dist/web/assets/panel-ND0J63d3.js +1 -0
- package/dist/web/assets/{session-indexes-0oT5HSS5.js → session-indexes-BsgjUoc1.js} +2 -2
- package/dist/web/assets/utils-C7BqyxxV.js +1 -0
- package/dist/web/index.html +7 -11
- package/package.json +4 -10
- package/dist/chunk-PROMURPM.js.map +0 -1
- package/dist/chunk-VAUC2W7I.js +0 -16119
- package/dist/chunk-VAUC2W7I.js.map +0 -1
- package/dist/dist-6QTGTYZT.js +0 -175
- package/dist/web/assets/ErrorBoundary-D11tWzfn.js +0 -1
- package/dist/web/assets/InteractiveReceipt-DYGyHeZd.js +0 -1
- package/dist/web/assets/OverviewScreen-DriPmCam.js +0 -1
- package/dist/web/assets/Projects-BPDIcUBK.js +0 -1
- package/dist/web/assets/SearchFilterBar-Bx73B5e9.js +0 -1
- package/dist/web/assets/SearchResultsPanel-A6_3TPhW.js +0 -1
- package/dist/web/assets/SessionDetail-BnpvDwtH.js +0 -54
- package/dist/web/assets/SmartTagChips-ux6idUGJ.js +0 -1
- package/dist/web/assets/agents-BkRP_ww3.js +0 -1
- package/dist/web/assets/code-metrics-UYFsCC7E.js +0 -1
- package/dist/web/assets/contract-V6QuD0UY.js +0 -1
- package/dist/web/assets/index-99ZxpV2y.js +0 -1410
- package/dist/web/assets/index-B2RqUgZ5.css +0 -1
- package/dist/web/assets/panel-C-gc79Gz.js +0 -1
- package/dist/web/assets/session-title-DdtlxtlR.js +0 -1
- package/dist/web/assets/utils-DgUgu15E.js +0 -1
- /package/dist/{dist-6QTGTYZT.js.map → chunk-QH2HOXJH.js.map} +0 -0
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
addCalendarDays,
|
|
4
|
+
buildSessionTree,
|
|
5
|
+
countCalendarDays,
|
|
6
|
+
filterSessionTreeEntriesByActivityWindow,
|
|
7
|
+
getProjectIdentityKey,
|
|
8
|
+
getSessionAgentKey,
|
|
9
|
+
getSessionReferenceKey,
|
|
10
|
+
toCalendarDayKey
|
|
11
|
+
} from "./chunk-W4PZHL2C.js";
|
|
12
|
+
|
|
13
|
+
// ../core/dist/chunk-OGQ63HC5.mjs
|
|
14
|
+
var COST_ABSOLUTE_TOLERANCE = 1e-8;
|
|
15
|
+
var COST_RELATIVE_TOLERANCE = 1e-6;
|
|
16
|
+
function indexFacts(facts) {
|
|
17
|
+
const messagesBySession = /* @__PURE__ */ new Map();
|
|
18
|
+
const summariesBySession = /* @__PURE__ */ new Map();
|
|
19
|
+
if (!facts) return { messagesBySession, summariesBySession };
|
|
20
|
+
for (const summary of facts.sessions) {
|
|
21
|
+
summariesBySession.set(getSessionReferenceKey(summary.reference), summary);
|
|
22
|
+
}
|
|
23
|
+
for (const message of facts.messages) {
|
|
24
|
+
const key = getSessionReferenceKey(message.reference);
|
|
25
|
+
const messages = messagesBySession.get(key);
|
|
26
|
+
if (messages) messages.push(message);
|
|
27
|
+
else messagesBySession.set(key, [message]);
|
|
28
|
+
}
|
|
29
|
+
return { messagesBySession, summariesBySession };
|
|
30
|
+
}
|
|
31
|
+
function nonNegative(value) {
|
|
32
|
+
return Math.max(0, Number(value ?? 0));
|
|
33
|
+
}
|
|
34
|
+
function costsReconcile(left, right) {
|
|
35
|
+
const tolerance = Math.max(
|
|
36
|
+
COST_ABSOLUTE_TOLERANCE,
|
|
37
|
+
Math.max(Math.abs(left), Math.abs(right)) * COST_RELATIVE_TOLERANCE
|
|
38
|
+
);
|
|
39
|
+
return Math.abs(left - right) <= tolerance;
|
|
40
|
+
}
|
|
41
|
+
function hasDetailedCost(session, summary) {
|
|
42
|
+
const totalCost = Math.max(0, session.stats.total_cost);
|
|
43
|
+
return totalCost > 0 && summary != null && summary.untimedMessageCost <= COST_ABSOLUTE_TOLERANCE && costsReconcile(summary.messageCost, totalCost);
|
|
44
|
+
}
|
|
45
|
+
function hasDetailedMessages(session, summary) {
|
|
46
|
+
return summary != null && summary.untimedMessageCount === 0 && summary.messageCount === nonNegative(session.stats.message_count);
|
|
47
|
+
}
|
|
48
|
+
function detailedOutputIncludesReasoning(session, summary) {
|
|
49
|
+
if (!summary) return null;
|
|
50
|
+
const stats = session.stats;
|
|
51
|
+
const sessionInput = nonNegative(stats.total_input_tokens);
|
|
52
|
+
const sessionOutput = nonNegative(stats.total_output_tokens);
|
|
53
|
+
const sessionCacheRead = nonNegative(stats.total_cache_read_tokens);
|
|
54
|
+
const sessionCacheCreate = nonNegative(stats.total_cache_create_tokens);
|
|
55
|
+
const sessionTotal = nonNegative(stats.total_tokens ?? sessionInput + sessionOutput);
|
|
56
|
+
const outputIncludesReasoning = summary.outputTokens + summary.reasoningTokens;
|
|
57
|
+
const outputMatches = summary.outputTokens === sessionOutput;
|
|
58
|
+
const outputWithReasoningMatches = outputIncludesReasoning === sessionOutput;
|
|
59
|
+
const effectiveOutput = outputMatches ? summary.outputTokens : outputWithReasoningMatches ? outputIncludesReasoning : null;
|
|
60
|
+
if (effectiveOutput == null || summary.inputTokens !== sessionInput || summary.cacheReadTokens !== sessionCacheRead || summary.cacheCreateTokens !== sessionCacheCreate || summary.inputTokens + effectiveOutput !== sessionTotal || summary.untimedInputTokens > 0 || summary.untimedOutputTokens > 0 || summary.untimedReasoningTokens > 0 || summary.untimedCacheReadTokens > 0 || summary.untimedCacheCreateTokens > 0) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return !outputMatches && outputWithReasoningMatches;
|
|
64
|
+
}
|
|
65
|
+
function isInWindow(time, from, to) {
|
|
66
|
+
return (from == null || time >= from) && time <= to;
|
|
67
|
+
}
|
|
68
|
+
function resolvedCostSource(session, source) {
|
|
69
|
+
return source ?? session.stats.cost_source ?? "recorded";
|
|
70
|
+
}
|
|
71
|
+
function fallbackModelCosts(summary, totalCost) {
|
|
72
|
+
if (!summary) return [];
|
|
73
|
+
const modelTotal = summary.modelCosts.reduce((sum, model) => sum + model.cost, 0);
|
|
74
|
+
return modelTotal > totalCost && !costsReconcile(modelTotal, totalCost) ? [] : summary.modelCosts;
|
|
75
|
+
}
|
|
76
|
+
function visitAttributedCosts(tree, options, visit) {
|
|
77
|
+
const factIndex = indexFacts(options.facts);
|
|
78
|
+
const factsAvailable = options.facts != null;
|
|
79
|
+
for (const entry of tree.entries) {
|
|
80
|
+
if (options.matchesEntry && !options.matchesEntry(entry.session)) continue;
|
|
81
|
+
const fallbackTime = entry.session.time_updated ?? entry.session.time_created;
|
|
82
|
+
const pending = [entry];
|
|
83
|
+
while (pending.length > 0) {
|
|
84
|
+
const node = pending.pop();
|
|
85
|
+
for (const child of node.children) pending.push(child);
|
|
86
|
+
const session = node.session;
|
|
87
|
+
const totalCost = Math.max(0, session.stats.total_cost);
|
|
88
|
+
if (totalCost <= 0) continue;
|
|
89
|
+
const key = getSessionReferenceKey(session.reference);
|
|
90
|
+
const summary = factIndex.summariesBySession.get(key);
|
|
91
|
+
if (factsAvailable && hasDetailedCost(session, summary)) {
|
|
92
|
+
for (const message of factIndex.messagesBySession.get(key) ?? []) {
|
|
93
|
+
if (!isInWindow(message.time, options.from, options.to) || message.cost <= 0) continue;
|
|
94
|
+
const source = resolvedCostSource(session, message.costSource);
|
|
95
|
+
visit({
|
|
96
|
+
entry,
|
|
97
|
+
time: message.time,
|
|
98
|
+
cost: message.cost,
|
|
99
|
+
source,
|
|
100
|
+
modelCosts: message.model ? [
|
|
101
|
+
{
|
|
102
|
+
model: message.model,
|
|
103
|
+
cost: message.cost,
|
|
104
|
+
costRecorded: source === "recorded" ? message.cost : 0
|
|
105
|
+
}
|
|
106
|
+
] : []
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (!isInWindow(fallbackTime, options.from, options.to)) continue;
|
|
112
|
+
visit({
|
|
113
|
+
entry,
|
|
114
|
+
time: fallbackTime,
|
|
115
|
+
cost: totalCost,
|
|
116
|
+
source: resolvedCostSource(session),
|
|
117
|
+
modelCosts: factsAvailable ? fallbackModelCosts(summary, totalCost) : []
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function visitAttributedUsage(tree, options, visit) {
|
|
123
|
+
const factIndex = indexFacts(options.facts);
|
|
124
|
+
const factsAvailable = options.facts != null;
|
|
125
|
+
for (const entry of tree.entries) {
|
|
126
|
+
if (options.matchesEntry && !options.matchesEntry(entry.session)) continue;
|
|
127
|
+
const fallbackTime = entry.session.time_updated ?? entry.session.time_created;
|
|
128
|
+
const pending = [entry];
|
|
129
|
+
while (pending.length > 0) {
|
|
130
|
+
const node = pending.pop();
|
|
131
|
+
for (const child of node.children) pending.push(child);
|
|
132
|
+
const session = node.session;
|
|
133
|
+
const key = getSessionReferenceKey(session.reference);
|
|
134
|
+
const summary = factIndex.summariesBySession.get(key);
|
|
135
|
+
const detailedMessages = factsAvailable && hasDetailedMessages(session, summary);
|
|
136
|
+
const outputIncludesReasoning = factsAvailable ? detailedOutputIncludesReasoning(session, summary) : null;
|
|
137
|
+
const detailedTokens = outputIncludesReasoning != null;
|
|
138
|
+
if (detailedMessages || detailedTokens) {
|
|
139
|
+
for (const message of factIndex.messagesBySession.get(key) ?? []) {
|
|
140
|
+
if (!isInWindow(message.time, options.from, options.to)) continue;
|
|
141
|
+
const outputTokens = detailedTokens ? message.outputTokens + (outputIncludesReasoning ? message.reasoningTokens : 0) : 0;
|
|
142
|
+
visit({
|
|
143
|
+
entry,
|
|
144
|
+
time: message.time,
|
|
145
|
+
messages: detailedMessages ? 1 : 0,
|
|
146
|
+
totalTokens: detailedTokens ? message.inputTokens + outputTokens : 0,
|
|
147
|
+
inputTokens: detailedTokens ? message.inputTokens : 0,
|
|
148
|
+
outputTokens,
|
|
149
|
+
cacheReadTokens: detailedTokens ? message.cacheReadTokens : 0,
|
|
150
|
+
cacheCreateTokens: detailedTokens ? message.cacheCreateTokens : 0
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (!isInWindow(fallbackTime, options.from, options.to)) continue;
|
|
155
|
+
if (!detailedMessages) {
|
|
156
|
+
visit({
|
|
157
|
+
entry,
|
|
158
|
+
time: fallbackTime,
|
|
159
|
+
messages: nonNegative(session.stats.message_count),
|
|
160
|
+
totalTokens: 0,
|
|
161
|
+
inputTokens: 0,
|
|
162
|
+
outputTokens: 0,
|
|
163
|
+
cacheReadTokens: 0,
|
|
164
|
+
cacheCreateTokens: 0
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (!detailedTokens) {
|
|
168
|
+
const inputTokens = nonNegative(session.stats.total_input_tokens);
|
|
169
|
+
const outputTokens = nonNegative(session.stats.total_output_tokens);
|
|
170
|
+
visit({
|
|
171
|
+
entry,
|
|
172
|
+
time: fallbackTime,
|
|
173
|
+
messages: 0,
|
|
174
|
+
totalTokens: nonNegative(session.stats.total_tokens ?? inputTokens + outputTokens),
|
|
175
|
+
inputTokens,
|
|
176
|
+
outputTokens,
|
|
177
|
+
cacheReadTokens: nonNegative(session.stats.total_cache_read_tokens),
|
|
178
|
+
cacheCreateTokens: nonNegative(session.stats.total_cache_create_tokens)
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
var DASHBOARD_RECENT_LIMIT = 10;
|
|
185
|
+
var DASHBOARD_PROJECT_LIMIT = 12;
|
|
186
|
+
var PROJECT_SPARKLINE_DAYS = 14;
|
|
187
|
+
var MODEL_COST_LIMIT = 20;
|
|
188
|
+
function getSessionAgentName(session) {
|
|
189
|
+
return getSessionAgentKey(session);
|
|
190
|
+
}
|
|
191
|
+
function getSessionActivityTime(session) {
|
|
192
|
+
return session.time_updated ?? session.time_created;
|
|
193
|
+
}
|
|
194
|
+
function matchesScope(session, scope) {
|
|
195
|
+
if (scope.agent && getSessionAgentName(session) !== scope.agent) return false;
|
|
196
|
+
if (scope.projectKind == null && scope.projectKey == null) return true;
|
|
197
|
+
const identity = session.project_identity;
|
|
198
|
+
return identity != null && identity.kind === scope.projectKind && identity.key === scope.projectKey;
|
|
199
|
+
}
|
|
200
|
+
function scopedEntries(tree, scope, from, to) {
|
|
201
|
+
return filterSessionTreeEntriesByActivityWindow(tree, from, to).filter(
|
|
202
|
+
(node) => matchesScope(node.session, scope)
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
function emptyDailyBucket(date) {
|
|
206
|
+
return {
|
|
207
|
+
date,
|
|
208
|
+
sessions: 0,
|
|
209
|
+
messages: 0,
|
|
210
|
+
cost: 0,
|
|
211
|
+
input: 0,
|
|
212
|
+
output: 0,
|
|
213
|
+
cache_read: 0,
|
|
214
|
+
cache_create: 0
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function createAccumulator(byAgentNames, scope, to) {
|
|
218
|
+
const agents = /* @__PURE__ */ new Map();
|
|
219
|
+
for (const name of byAgentNames) {
|
|
220
|
+
const key = name.toLowerCase();
|
|
221
|
+
if (scope.agent && key !== scope.agent) continue;
|
|
222
|
+
agents.set(key, { name, sessions: 0, messages: 0, tokens: 0, cost: 0 });
|
|
223
|
+
}
|
|
224
|
+
const sparklineSlots = /* @__PURE__ */ new Map();
|
|
225
|
+
for (let slot = 0; slot < PROJECT_SPARKLINE_DAYS; slot += 1) {
|
|
226
|
+
const day = addCalendarDays(to, slot - (PROJECT_SPARKLINE_DAYS - 1));
|
|
227
|
+
sparklineSlots.set(toCalendarDayKey(day), slot);
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
sessions: 0,
|
|
231
|
+
messages: 0,
|
|
232
|
+
tokens: 0,
|
|
233
|
+
cost: 0,
|
|
234
|
+
costRecorded: 0,
|
|
235
|
+
costEstimated: 0,
|
|
236
|
+
cacheReadTokens: 0,
|
|
237
|
+
hasEstimatedCost: false,
|
|
238
|
+
agents,
|
|
239
|
+
agentKeys: /* @__PURE__ */ new Set(),
|
|
240
|
+
daily: /* @__PURE__ */ new Map(),
|
|
241
|
+
models: /* @__PURE__ */ new Map(),
|
|
242
|
+
modelCosts: /* @__PURE__ */ new Map(),
|
|
243
|
+
projects: /* @__PURE__ */ new Map(),
|
|
244
|
+
projectKeys: /* @__PURE__ */ new Set(),
|
|
245
|
+
sparklineSlots,
|
|
246
|
+
recent: []
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function foldModelUsage(node, into) {
|
|
250
|
+
if (node.session.model_usage) {
|
|
251
|
+
for (const [model, tokens] of Object.entries(node.session.model_usage)) {
|
|
252
|
+
into.set(model, (into.get(model) ?? 0) + tokens);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
for (const child of node.children) foldModelUsage(child, into);
|
|
256
|
+
}
|
|
257
|
+
function getOrCreateProject(session, acc) {
|
|
258
|
+
const identity = session.project_identity;
|
|
259
|
+
if (!identity) return void 0;
|
|
260
|
+
const key = getProjectIdentityKey(identity);
|
|
261
|
+
let project = acc.projects.get(key);
|
|
262
|
+
if (!project) {
|
|
263
|
+
project = {
|
|
264
|
+
identityKind: identity.kind,
|
|
265
|
+
identityKey: identity.key,
|
|
266
|
+
displayName: identity.displayName,
|
|
267
|
+
sessions: 0,
|
|
268
|
+
messages: 0,
|
|
269
|
+
tokens: 0,
|
|
270
|
+
cost: 0,
|
|
271
|
+
hasEstimatedCost: false,
|
|
272
|
+
agentSessions: /* @__PURE__ */ new Map(),
|
|
273
|
+
sparkline: Array.from({ length: PROJECT_SPARKLINE_DAYS }, () => 0)
|
|
274
|
+
};
|
|
275
|
+
acc.projects.set(key, project);
|
|
276
|
+
}
|
|
277
|
+
return project;
|
|
278
|
+
}
|
|
279
|
+
function trackProjectActivity(node, acc, agentKey) {
|
|
280
|
+
const identity = node.session.project_identity;
|
|
281
|
+
const project = getOrCreateProject(node.session, acc);
|
|
282
|
+
if (!identity || !project) return;
|
|
283
|
+
project.sessions += 1;
|
|
284
|
+
project.agentSessions.set(agentKey, (project.agentSessions.get(agentKey) ?? 0) + 1);
|
|
285
|
+
acc.projectKeys.add(getProjectIdentityKey(identity));
|
|
286
|
+
}
|
|
287
|
+
function accumulateActivity(node, acc) {
|
|
288
|
+
const session = node.session;
|
|
289
|
+
const activity = getSessionActivityTime(session);
|
|
290
|
+
const agentKey = getSessionAgentName(session);
|
|
291
|
+
acc.sessions += 1;
|
|
292
|
+
acc.agentKeys.add(agentKey);
|
|
293
|
+
const metric = acc.agents.get(agentKey);
|
|
294
|
+
if (metric) {
|
|
295
|
+
metric.sessions += 1;
|
|
296
|
+
}
|
|
297
|
+
const dayKey = toCalendarDayKey(activity);
|
|
298
|
+
let bucket = acc.daily.get(dayKey);
|
|
299
|
+
if (!bucket) {
|
|
300
|
+
bucket = emptyDailyBucket(dayKey);
|
|
301
|
+
acc.daily.set(dayKey, bucket);
|
|
302
|
+
}
|
|
303
|
+
bucket.sessions += 1;
|
|
304
|
+
const usage = /* @__PURE__ */ new Map();
|
|
305
|
+
foldModelUsage(node, usage);
|
|
306
|
+
for (const [model, tokens] of usage) {
|
|
307
|
+
const entry = acc.models.get(model);
|
|
308
|
+
if (entry) {
|
|
309
|
+
entry.tokens += tokens;
|
|
310
|
+
entry.sessions += 1;
|
|
311
|
+
} else {
|
|
312
|
+
acc.models.set(model, { tokens, sessions: 1 });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
trackProjectActivity(node, acc, agentKey);
|
|
316
|
+
let recentIndex = acc.recent.length;
|
|
317
|
+
for (let i = 0; i < acc.recent.length; i += 1) {
|
|
318
|
+
if (activity > acc.recent[i].activity) {
|
|
319
|
+
recentIndex = i;
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (recentIndex < DASHBOARD_RECENT_LIMIT) {
|
|
324
|
+
acc.recent.splice(recentIndex, 0, { session, activity });
|
|
325
|
+
if (acc.recent.length > DASHBOARD_RECENT_LIMIT) acc.recent.pop();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function addUsage(acc, entry, time, usage) {
|
|
329
|
+
const { messages, totalTokens, inputTokens, outputTokens, cacheReadTokens, cacheCreateTokens } = usage;
|
|
330
|
+
if (messages <= 0 && totalTokens <= 0 && inputTokens <= 0 && outputTokens <= 0 && cacheReadTokens <= 0 && cacheCreateTokens <= 0) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
acc.messages += messages;
|
|
334
|
+
acc.tokens += totalTokens;
|
|
335
|
+
acc.cacheReadTokens += cacheReadTokens;
|
|
336
|
+
const agentKey = getSessionAgentName(entry.session);
|
|
337
|
+
let metric = acc.agents.get(agentKey);
|
|
338
|
+
if (!metric) {
|
|
339
|
+
metric = { name: agentKey, sessions: 0, messages: 0, tokens: 0, cost: 0 };
|
|
340
|
+
acc.agents.set(agentKey, metric);
|
|
341
|
+
}
|
|
342
|
+
metric.messages += messages;
|
|
343
|
+
metric.tokens += totalTokens;
|
|
344
|
+
const dayKey = toCalendarDayKey(time);
|
|
345
|
+
let bucket = acc.daily.get(dayKey);
|
|
346
|
+
if (!bucket) {
|
|
347
|
+
bucket = emptyDailyBucket(dayKey);
|
|
348
|
+
acc.daily.set(dayKey, bucket);
|
|
349
|
+
}
|
|
350
|
+
bucket.messages += messages;
|
|
351
|
+
bucket.input += Math.max(0, inputTokens - cacheReadTokens - cacheCreateTokens);
|
|
352
|
+
bucket.output += outputTokens;
|
|
353
|
+
bucket.cache_read += cacheReadTokens;
|
|
354
|
+
bucket.cache_create += cacheCreateTokens;
|
|
355
|
+
const project = getOrCreateProject(entry.session, acc);
|
|
356
|
+
if (!project) return;
|
|
357
|
+
project.messages += messages;
|
|
358
|
+
project.tokens += totalTokens;
|
|
359
|
+
}
|
|
360
|
+
function accumulateUsage(tree, scope, acc, from, to, costFacts) {
|
|
361
|
+
visitAttributedUsage(
|
|
362
|
+
tree,
|
|
363
|
+
{ from, to, facts: costFacts, matchesEntry: (session) => matchesScope(session, scope) },
|
|
364
|
+
({ entry, time, ...usage }) => addUsage(acc, entry, time, usage)
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
function addModelCost(acc, model, cost, recordedCost) {
|
|
368
|
+
if (!model || cost <= 0) return;
|
|
369
|
+
const recorded = Math.max(0, Math.min(cost, recordedCost));
|
|
370
|
+
const current = acc.modelCosts.get(model);
|
|
371
|
+
if (current) {
|
|
372
|
+
current.cost += cost;
|
|
373
|
+
current.costRecorded += recorded;
|
|
374
|
+
current.costEstimated += cost - recorded;
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
acc.modelCosts.set(model, {
|
|
378
|
+
model,
|
|
379
|
+
cost,
|
|
380
|
+
costRecorded: recorded,
|
|
381
|
+
costEstimated: cost - recorded
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
function addCost(acc, entry, time, cost, source) {
|
|
385
|
+
if (cost <= 0) return;
|
|
386
|
+
acc.cost += cost;
|
|
387
|
+
if (source === "recorded") acc.costRecorded += cost;
|
|
388
|
+
else {
|
|
389
|
+
acc.costEstimated += cost;
|
|
390
|
+
acc.hasEstimatedCost = true;
|
|
391
|
+
}
|
|
392
|
+
const agentKey = getSessionAgentName(entry.session);
|
|
393
|
+
let metric = acc.agents.get(agentKey);
|
|
394
|
+
if (!metric) {
|
|
395
|
+
metric = { name: agentKey, sessions: 0, messages: 0, tokens: 0, cost: 0 };
|
|
396
|
+
acc.agents.set(agentKey, metric);
|
|
397
|
+
}
|
|
398
|
+
metric.cost += cost;
|
|
399
|
+
const dayKey = toCalendarDayKey(time);
|
|
400
|
+
let bucket = acc.daily.get(dayKey);
|
|
401
|
+
if (!bucket) {
|
|
402
|
+
bucket = emptyDailyBucket(dayKey);
|
|
403
|
+
acc.daily.set(dayKey, bucket);
|
|
404
|
+
}
|
|
405
|
+
bucket.cost += cost;
|
|
406
|
+
const project = getOrCreateProject(entry.session, acc);
|
|
407
|
+
if (!project) return;
|
|
408
|
+
project.cost += cost;
|
|
409
|
+
if (source === "estimated") project.hasEstimatedCost = true;
|
|
410
|
+
const slot = acc.sparklineSlots.get(dayKey);
|
|
411
|
+
if (slot != null) project.sparkline[slot] += cost;
|
|
412
|
+
}
|
|
413
|
+
function accumulateCosts(tree, scope, acc, from, to, costFacts) {
|
|
414
|
+
visitAttributedCosts(
|
|
415
|
+
tree,
|
|
416
|
+
{ from, to, facts: costFacts, matchesEntry: (session) => matchesScope(session, scope) },
|
|
417
|
+
({ entry, time, cost, source, modelCosts }) => {
|
|
418
|
+
addCost(acc, entry, time, cost, source);
|
|
419
|
+
for (const model of modelCosts) {
|
|
420
|
+
addModelCost(acc, model.model, model.cost, model.costRecorded);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
function toPreviousTotals(acc) {
|
|
426
|
+
return { sessions: acc.sessions, messages: acc.messages, tokens: acc.tokens, cost: acc.cost };
|
|
427
|
+
}
|
|
428
|
+
function toProjectStat(project) {
|
|
429
|
+
return {
|
|
430
|
+
identityKind: project.identityKind,
|
|
431
|
+
identityKey: project.identityKey,
|
|
432
|
+
displayName: project.displayName,
|
|
433
|
+
sessions: project.sessions,
|
|
434
|
+
messages: project.messages,
|
|
435
|
+
tokens: project.tokens,
|
|
436
|
+
cost: project.cost,
|
|
437
|
+
cost_source: project.cost > 0 ? project.hasEstimatedCost ? "estimated" : "recorded" : void 0,
|
|
438
|
+
agents: [...project.agentSessions.entries()].sort((a, b) => b[1] - a[1]).map(([agentKey]) => agentKey),
|
|
439
|
+
sparkline: project.sparkline
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function buildDashboard(sessions, options) {
|
|
443
|
+
const { byAgentNames, scope, from, to, agentInfoMap, compare, costFacts } = options;
|
|
444
|
+
const tree = buildSessionTree(sessions);
|
|
445
|
+
const costFactsAvailable = costFacts != null;
|
|
446
|
+
const acc = createAccumulator(byAgentNames, scope, to);
|
|
447
|
+
if (from != null) {
|
|
448
|
+
const bucketDays = countCalendarDays(from, to);
|
|
449
|
+
for (let i = 0; i < bucketDays; i += 1) {
|
|
450
|
+
const key = toCalendarDayKey(addCalendarDays(from, i));
|
|
451
|
+
acc.daily.set(key, emptyDailyBucket(key));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
for (const node of scopedEntries(tree, scope, from, to)) accumulateActivity(node, acc);
|
|
455
|
+
accumulateUsage(tree, scope, acc, from, to, costFacts);
|
|
456
|
+
accumulateCosts(tree, scope, acc, from, to, costFacts);
|
|
457
|
+
let previous;
|
|
458
|
+
if (compare) {
|
|
459
|
+
const compareAcc = createAccumulator(byAgentNames, scope, compare.to);
|
|
460
|
+
for (const node of scopedEntries(tree, scope, compare.from, compare.to)) {
|
|
461
|
+
accumulateActivity(node, compareAcc);
|
|
462
|
+
}
|
|
463
|
+
accumulateUsage(tree, scope, compareAcc, compare.from, compare.to, costFacts);
|
|
464
|
+
accumulateCosts(tree, scope, compareAcc, compare.from, compare.to, costFacts);
|
|
465
|
+
previous = toPreviousTotals(compareAcc);
|
|
466
|
+
}
|
|
467
|
+
const perAgent = [...acc.agents.values()].map((metrics) => {
|
|
468
|
+
const info = agentInfoMap?.get(metrics.name);
|
|
469
|
+
return {
|
|
470
|
+
name: metrics.name,
|
|
471
|
+
displayName: info?.displayName ?? metrics.name,
|
|
472
|
+
icon: info?.icon ?? "",
|
|
473
|
+
iconColored: info?.iconColored,
|
|
474
|
+
sessions: metrics.sessions,
|
|
475
|
+
messages: metrics.messages,
|
|
476
|
+
tokens: metrics.tokens,
|
|
477
|
+
cost: metrics.cost
|
|
478
|
+
};
|
|
479
|
+
}).filter((item) => item.sessions > 0 || item.messages > 0 || item.tokens > 0 || item.cost > 0).sort((a, b) => b.sessions - a.sessions || b.cost - a.cost);
|
|
480
|
+
const dailyActivity = [...acc.daily.values()].sort((a, b) => a.date.localeCompare(b.date));
|
|
481
|
+
const modelDistribution = [...acc.models.entries()].map(([model, { tokens, sessions: count }]) => ({ model, tokens, sessions: count })).sort((a, b) => b.tokens - a.tokens);
|
|
482
|
+
const modelCost = costFactsAvailable ? [...acc.modelCosts.values()].sort((a, b) => b.cost - a.cost).slice(0, MODEL_COST_LIMIT) : null;
|
|
483
|
+
const rankedProjects = [...acc.projects.values()].sort((a, b) => b.cost - a.cost);
|
|
484
|
+
const perProject = rankedProjects.slice(0, DASHBOARD_PROJECT_LIMIT).map(toProjectStat);
|
|
485
|
+
const projectRollup = rankedProjects.slice(DASHBOARD_PROJECT_LIMIT).reduce(
|
|
486
|
+
(rollup, project) => ({
|
|
487
|
+
projects: rollup.projects + 1,
|
|
488
|
+
sessions: rollup.sessions + project.sessions,
|
|
489
|
+
tokens: rollup.tokens + project.tokens,
|
|
490
|
+
cost: rollup.cost + project.cost
|
|
491
|
+
}),
|
|
492
|
+
{ projects: 0, sessions: 0, tokens: 0, cost: 0 }
|
|
493
|
+
);
|
|
494
|
+
const recentSessions = acc.recent.map(({ session }) => ({
|
|
495
|
+
reference: session.reference,
|
|
496
|
+
session
|
|
497
|
+
}));
|
|
498
|
+
const latest = acc.recent[0];
|
|
499
|
+
return {
|
|
500
|
+
totals: {
|
|
501
|
+
sessions: acc.sessions,
|
|
502
|
+
messages: acc.messages,
|
|
503
|
+
tokens: acc.tokens,
|
|
504
|
+
cost: acc.cost,
|
|
505
|
+
costRecorded: acc.costRecorded,
|
|
506
|
+
costEstimated: acc.costEstimated,
|
|
507
|
+
cacheReadTokens: acc.cacheReadTokens,
|
|
508
|
+
cost_source: acc.cost > 0 ? acc.hasEstimatedCost ? "estimated" : "recorded" : void 0,
|
|
509
|
+
latestActivity: latest?.activity || void 0,
|
|
510
|
+
latestActivityProject: latest?.session.project_identity?.displayName,
|
|
511
|
+
latestActivityAgent: latest ? getSessionAgentName(latest.session) : void 0,
|
|
512
|
+
previous
|
|
513
|
+
},
|
|
514
|
+
scopeCounts: { projects: acc.projectKeys.size, agents: acc.agentKeys.size },
|
|
515
|
+
perAgent,
|
|
516
|
+
dailyActivity,
|
|
517
|
+
modelDistribution,
|
|
518
|
+
modelCost,
|
|
519
|
+
perProject,
|
|
520
|
+
projectRollup,
|
|
521
|
+
recentSessions
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// ../core/dist/runtime/projects.mjs
|
|
526
|
+
function summarizeProjects(projects) {
|
|
527
|
+
const summary = {
|
|
528
|
+
projects: projects.length,
|
|
529
|
+
sessions: 0,
|
|
530
|
+
tokens: 0,
|
|
531
|
+
cost: 0,
|
|
532
|
+
latestActivity: null
|
|
533
|
+
};
|
|
534
|
+
for (const project of projects) {
|
|
535
|
+
summary.sessions += project.sessionCount;
|
|
536
|
+
summary.tokens += project.tokens;
|
|
537
|
+
summary.cost += project.cost;
|
|
538
|
+
if (project.lastActivity != null) {
|
|
539
|
+
summary.latestActivity = summary.latestActivity == null ? project.lastActivity : Math.max(summary.latestActivity, project.lastActivity);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return summary;
|
|
543
|
+
}
|
|
544
|
+
function emptyMetrics() {
|
|
545
|
+
return {
|
|
546
|
+
sessions: 0,
|
|
547
|
+
messages: 0,
|
|
548
|
+
tokens: 0,
|
|
549
|
+
cost: 0,
|
|
550
|
+
hasEstimatedCost: false,
|
|
551
|
+
agentStats: /* @__PURE__ */ new Map()
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
function attachProjectMetricsFromTree(projects, tree, from, to, costFacts) {
|
|
555
|
+
return attachProjectMetricsToEntries(
|
|
556
|
+
projects,
|
|
557
|
+
tree,
|
|
558
|
+
filterSessionTreeEntriesByActivityWindow(tree, from, to),
|
|
559
|
+
from,
|
|
560
|
+
to,
|
|
561
|
+
costFacts
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
function attachProjectMetricsToEntries(projects, tree, entries, from, to, costFacts) {
|
|
565
|
+
const metrics = /* @__PURE__ */ new Map();
|
|
566
|
+
for (const node of entries) {
|
|
567
|
+
const identity = node.session.project_identity;
|
|
568
|
+
if (!identity) continue;
|
|
569
|
+
const key = getProjectIdentityKey(identity);
|
|
570
|
+
let current = metrics.get(key);
|
|
571
|
+
if (!current) {
|
|
572
|
+
current = emptyMetrics();
|
|
573
|
+
metrics.set(key, current);
|
|
574
|
+
}
|
|
575
|
+
current.sessions += 1;
|
|
576
|
+
const agentName = getSessionAgentName(node.session);
|
|
577
|
+
const agent = current.agentStats.get(agentName);
|
|
578
|
+
if (agent) {
|
|
579
|
+
agent.sessions += 1;
|
|
580
|
+
} else {
|
|
581
|
+
current.agentStats.set(agentName, {
|
|
582
|
+
name: agentName,
|
|
583
|
+
sessions: 1,
|
|
584
|
+
messages: 0,
|
|
585
|
+
tokens: 0,
|
|
586
|
+
cost: 0
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
visitAttributedUsage(
|
|
591
|
+
tree,
|
|
592
|
+
{ from, to: to ?? Number.POSITIVE_INFINITY, facts: costFacts },
|
|
593
|
+
({ entry, messages, totalTokens }) => {
|
|
594
|
+
const identity = entry.session.project_identity;
|
|
595
|
+
if (!identity) return;
|
|
596
|
+
const key = getProjectIdentityKey(identity);
|
|
597
|
+
let current = metrics.get(key);
|
|
598
|
+
if (!current) {
|
|
599
|
+
current = emptyMetrics();
|
|
600
|
+
metrics.set(key, current);
|
|
601
|
+
}
|
|
602
|
+
current.messages += messages;
|
|
603
|
+
current.tokens += totalTokens;
|
|
604
|
+
const agentName = getSessionAgentName(entry.session);
|
|
605
|
+
const agent = current.agentStats.get(agentName);
|
|
606
|
+
if (agent) {
|
|
607
|
+
agent.messages += messages;
|
|
608
|
+
agent.tokens += totalTokens;
|
|
609
|
+
} else {
|
|
610
|
+
current.agentStats.set(agentName, {
|
|
611
|
+
name: agentName,
|
|
612
|
+
sessions: 0,
|
|
613
|
+
messages,
|
|
614
|
+
tokens: totalTokens,
|
|
615
|
+
cost: 0
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
);
|
|
620
|
+
visitAttributedCosts(
|
|
621
|
+
tree,
|
|
622
|
+
{ from, to: to ?? Number.POSITIVE_INFINITY, facts: costFacts },
|
|
623
|
+
({ entry, cost, source }) => {
|
|
624
|
+
const identity = entry.session.project_identity;
|
|
625
|
+
if (!identity) return;
|
|
626
|
+
const key = getProjectIdentityKey(identity);
|
|
627
|
+
let current = metrics.get(key);
|
|
628
|
+
if (!current) {
|
|
629
|
+
current = emptyMetrics();
|
|
630
|
+
metrics.set(key, current);
|
|
631
|
+
}
|
|
632
|
+
current.cost += cost;
|
|
633
|
+
if (source === "estimated") current.hasEstimatedCost = true;
|
|
634
|
+
const agentName = getSessionAgentName(entry.session);
|
|
635
|
+
const agent = current.agentStats.get(agentName);
|
|
636
|
+
if (agent) agent.cost += cost;
|
|
637
|
+
else {
|
|
638
|
+
current.agentStats.set(agentName, {
|
|
639
|
+
name: agentName,
|
|
640
|
+
sessions: 0,
|
|
641
|
+
messages: 0,
|
|
642
|
+
tokens: 0,
|
|
643
|
+
cost
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
);
|
|
648
|
+
return projects.map((project) => {
|
|
649
|
+
const metric = metrics.get(
|
|
650
|
+
getProjectIdentityKey({ kind: project.identityKind, key: project.identityKey })
|
|
651
|
+
);
|
|
652
|
+
return {
|
|
653
|
+
...project,
|
|
654
|
+
sessionCount: metric?.sessions ?? 0,
|
|
655
|
+
messages: metric?.messages ?? 0,
|
|
656
|
+
tokens: metric?.tokens ?? 0,
|
|
657
|
+
cost: metric?.cost ?? 0,
|
|
658
|
+
cost_source: metric && metric.cost > 0 ? metric.hasEstimatedCost ? "estimated" : "recorded" : void 0,
|
|
659
|
+
agentStats: [...metric?.agentStats.values() ?? []].sort((a, b) => b.sessions - a.sessions)
|
|
660
|
+
};
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
export {
|
|
665
|
+
getSessionActivityTime,
|
|
666
|
+
buildDashboard,
|
|
667
|
+
summarizeProjects,
|
|
668
|
+
attachProjectMetricsFromTree
|
|
669
|
+
};
|
|
670
|
+
//# sourceMappingURL=chunk-MBZ4VV4C.js.map
|