@leo000001/opencode-quota-sidebar 3.0.10 → 4.0.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/CHANGELOG.md +0 -1
- package/README.md +163 -42
- package/README.zh-CN.md +163 -42
- package/SECURITY.md +1 -1
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +354 -0
- package/dist/cli_render.d.ts +17 -0
- package/dist/cli_render.js +292 -0
- package/dist/events.d.ts +1 -1
- package/dist/events.js +2 -2
- package/dist/format.d.ts +4 -0
- package/dist/format.js +391 -49
- package/dist/history_messages.d.ts +8 -0
- package/dist/history_messages.js +157 -0
- package/dist/history_usage.d.ts +93 -0
- package/dist/history_usage.js +251 -0
- package/dist/index.js +29 -4
- package/dist/period.d.ts +29 -1
- package/dist/period.js +187 -9
- package/dist/provider_catalog.d.ts +8 -0
- package/dist/provider_catalog.js +68 -0
- package/dist/providers/core/anthropic.d.ts +1 -1
- package/dist/providers/core/anthropic.js +69 -45
- package/dist/providers/core/openai.js +38 -2
- package/dist/providers/index.d.ts +1 -2
- package/dist/providers/index.js +1 -3
- package/dist/quota.d.ts +4 -2
- package/dist/quota.js +18 -21
- package/dist/quota_render.d.ts +1 -1
- package/dist/quota_render.js +23 -24
- package/dist/quota_service.d.ts +1 -0
- package/dist/quota_service.js +151 -19
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +4 -4
- package/dist/storage_dates.d.ts +1 -1
- package/dist/storage_dates.js +8 -5
- package/dist/storage_parse.js +23 -1
- package/dist/supported_quota.d.ts +4 -0
- package/dist/supported_quota.js +36 -0
- package/dist/title.js +21 -10
- package/dist/tools.d.ts +14 -3
- package/dist/tools.js +54 -2
- package/dist/tui.tsx +17 -6
- package/dist/tui_helpers.js +11 -6
- package/dist/types.d.ts +8 -0
- package/dist/usage.d.ts +18 -0
- package/dist/usage.js +93 -9
- package/dist/usage_service.d.ts +4 -1
- package/dist/usage_service.js +193 -189
- package/package.json +4 -1
- package/quota-sidebar.config.example.json +36 -45
- package/dist/providers/third_party/xyai.d.ts +0 -2
- package/dist/providers/third_party/xyai.js +0 -348
package/dist/tools.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as z from 'zod';
|
|
2
2
|
import type { QuotaSnapshot } from './types.js';
|
|
3
3
|
import type { UsageSummary } from './usage.js';
|
|
4
|
+
import type { HistoryPeriod } from './period.js';
|
|
5
|
+
import type { HistoryUsageResult } from './usage_service.js';
|
|
4
6
|
type ToolContext = {
|
|
5
7
|
sessionID: string;
|
|
6
8
|
};
|
|
@@ -19,6 +21,8 @@ export declare function createQuotaSidebarTools(deps: {
|
|
|
19
21
|
restoreSessionTitle?: (sessionID: string) => Promise<boolean>;
|
|
20
22
|
showToast: (period: 'session' | 'day' | 'week' | 'month' | 'toggle', message: string) => Promise<void>;
|
|
21
23
|
summarizeForTool: (period: 'session' | 'day' | 'week' | 'month', sessionID: string, includeChildren: boolean) => Promise<UsageSummary>;
|
|
24
|
+
summarizeHistoryForTool: (period: HistoryPeriod, since: string) => Promise<HistoryUsageResult>;
|
|
25
|
+
listCurrentProviderIDs?: () => Promise<Set<string>>;
|
|
22
26
|
getQuotaSnapshots: (providerIDs: string[], options?: {
|
|
23
27
|
allowDefault?: boolean;
|
|
24
28
|
}) => Promise<QuotaSnapshot[]>;
|
|
@@ -29,6 +33,9 @@ export declare function createQuotaSidebarTools(deps: {
|
|
|
29
33
|
showCost?: boolean;
|
|
30
34
|
width?: number;
|
|
31
35
|
}) => string;
|
|
36
|
+
renderHistoryMarkdownReport: (result: HistoryUsageResult, quotas: QuotaSnapshot[], options?: {
|
|
37
|
+
showCost?: boolean;
|
|
38
|
+
}) => string;
|
|
32
39
|
config: {
|
|
33
40
|
sidebar: {
|
|
34
41
|
showCost: boolean;
|
|
@@ -42,16 +49,20 @@ export declare function createQuotaSidebarTools(deps: {
|
|
|
42
49
|
description: string;
|
|
43
50
|
args: {
|
|
44
51
|
period: z.ZodOptional<z.ZodEnum<{
|
|
45
|
-
day: "day";
|
|
46
|
-
week: "week";
|
|
47
52
|
month: "month";
|
|
53
|
+
day: "day";
|
|
48
54
|
session: "session";
|
|
55
|
+
week: "week";
|
|
49
56
|
}>>;
|
|
57
|
+
since: z.ZodOptional<z.ZodString>;
|
|
58
|
+
last: z.ZodOptional<z.ZodNumber>;
|
|
50
59
|
toast: z.ZodOptional<z.ZodBoolean>;
|
|
51
60
|
includeChildren: z.ZodOptional<z.ZodBoolean>;
|
|
52
61
|
};
|
|
53
62
|
execute: (args: {
|
|
54
|
-
period?: "
|
|
63
|
+
period?: "month" | "day" | "session" | "week" | undefined;
|
|
64
|
+
since?: string | undefined;
|
|
65
|
+
last?: number | undefined;
|
|
55
66
|
toast?: boolean | undefined;
|
|
56
67
|
includeChildren?: boolean | undefined;
|
|
57
68
|
}, context: ToolContext) => Promise<string>;
|
package/dist/tools.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as z from 'zod';
|
|
2
|
+
import { sinceFromLast } from './period.js';
|
|
3
|
+
import { filterHistoryProvidersForDisplay, filterUsageProvidersForDisplay, } from './provider_catalog.js';
|
|
2
4
|
function tool(input) {
|
|
3
5
|
return input;
|
|
4
6
|
}
|
|
@@ -16,6 +18,16 @@ export function createQuotaSidebarTools(deps) {
|
|
|
16
18
|
description: 'Show usage and quota summary for session/day/week/month. Returns the full markdown report with totals, highlights, provider table, and subscription quota so callers can present the report directly to the user.',
|
|
17
19
|
args: {
|
|
18
20
|
period: z.enum(['session', 'day', 'week', 'month']).optional(),
|
|
21
|
+
since: z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('Historical start date: `YYYY-MM` or `YYYY-MM-DD`.'),
|
|
25
|
+
last: z
|
|
26
|
+
.number()
|
|
27
|
+
.int()
|
|
28
|
+
.positive()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe('Relative history length. Examples: `period=day,last=7`, `period=week,last=8`, `period=month,last=6`.'),
|
|
19
31
|
toast: z.boolean().optional(),
|
|
20
32
|
includeChildren: z
|
|
21
33
|
.boolean()
|
|
@@ -23,11 +35,51 @@ export function createQuotaSidebarTools(deps) {
|
|
|
23
35
|
.describe('For period=session, include descendant subagent sessions in usage aggregation.'),
|
|
24
36
|
},
|
|
25
37
|
execute: async (args, context) => {
|
|
26
|
-
const period = args.period || 'session';
|
|
38
|
+
const period = args.period || (args.since || args.last ? 'month' : 'session');
|
|
39
|
+
const since = args.since?.trim();
|
|
40
|
+
const last = args.last;
|
|
41
|
+
if (since && last !== undefined) {
|
|
42
|
+
throw new Error('`since` and `last` cannot be used together');
|
|
43
|
+
}
|
|
44
|
+
if (period === 'session' && since) {
|
|
45
|
+
throw new Error('`since` is not supported when `period=session`');
|
|
46
|
+
}
|
|
47
|
+
if (period === 'session' && last !== undefined) {
|
|
48
|
+
throw new Error('`last` is not supported when `period=session`');
|
|
49
|
+
}
|
|
50
|
+
const resolvedSince = since ||
|
|
51
|
+
(period !== 'session' && last !== undefined
|
|
52
|
+
? sinceFromLast(period, last)
|
|
53
|
+
: undefined);
|
|
54
|
+
const allowedProviderIDs = await deps
|
|
55
|
+
.listCurrentProviderIDs?.()
|
|
56
|
+
.catch(() => new Set());
|
|
57
|
+
if (period !== 'session' && resolvedSince) {
|
|
58
|
+
const historyRaw = await deps.summarizeHistoryForTool(period, resolvedSince);
|
|
59
|
+
const history = allowedProviderIDs
|
|
60
|
+
? filterHistoryProvidersForDisplay(historyRaw, allowedProviderIDs)
|
|
61
|
+
: historyRaw;
|
|
62
|
+
const quotas = await deps.getQuotaSnapshots([], {
|
|
63
|
+
allowDefault: true,
|
|
64
|
+
});
|
|
65
|
+
const markdown = deps.renderHistoryMarkdownReport(history, quotas, {
|
|
66
|
+
showCost: deps.config.sidebar.showCost,
|
|
67
|
+
});
|
|
68
|
+
if (args.toast === true) {
|
|
69
|
+
await deps.showToast(period, deps.renderToastMessage(period, history.total, quotas, {
|
|
70
|
+
showCost: deps.config.sidebar.showCost,
|
|
71
|
+
width: Math.max(44, deps.config.sidebar.width + 18),
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
return markdown;
|
|
75
|
+
}
|
|
27
76
|
const includeChildren = period === 'session'
|
|
28
77
|
? (args.includeChildren ?? deps.config.sidebar.includeChildren)
|
|
29
78
|
: false;
|
|
30
|
-
const
|
|
79
|
+
const usageRaw = await deps.summarizeForTool(period, context.sessionID, includeChildren);
|
|
80
|
+
const usage = allowedProviderIDs
|
|
81
|
+
? filterUsageProvidersForDisplay(usageRaw, allowedProviderIDs)
|
|
82
|
+
: usageRaw;
|
|
31
83
|
// For quota_summary, always show all subscription quota balances,
|
|
32
84
|
// regardless of which providers were used in the session.
|
|
33
85
|
const quotas = await deps.getQuotaSnapshots([], { allowDefault: true });
|
package/dist/tui.tsx
CHANGED
|
@@ -25,7 +25,11 @@ import {
|
|
|
25
25
|
} from './storage.js'
|
|
26
26
|
import { looksDecorated, normalizeBaseTitle } from './title.js'
|
|
27
27
|
import type { QuotaSidebarConfig } from './types.js'
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
fromCachedSessionUsage,
|
|
30
|
+
mergeUsage,
|
|
31
|
+
summarizeMessages,
|
|
32
|
+
} from './usage.js'
|
|
29
33
|
|
|
30
34
|
const id = 'leo.quota-sidebar'
|
|
31
35
|
const INTERNAL_CONTEXT_PLUGIN_ID = 'internal:sidebar-context'
|
|
@@ -89,11 +93,18 @@ async function loadSidebarPanel(
|
|
|
89
93
|
|
|
90
94
|
const liveUsage = summarizeMessages(liveEntries, 0, 1)
|
|
91
95
|
const cachedUsage = session?.sidebarPanel?.usage || session?.usage
|
|
92
|
-
const
|
|
96
|
+
const persistedUsage = cachedUsage
|
|
93
97
|
? fromCachedSessionUsage(cachedUsage)
|
|
94
|
-
:
|
|
98
|
+
: undefined
|
|
99
|
+
const usage =
|
|
100
|
+
liveUsage.assistantMessages > 0 &&
|
|
101
|
+
(!persistedUsage ||
|
|
102
|
+
liveUsage.assistantMessages > persistedUsage.assistantMessages ||
|
|
103
|
+
(liveUsage.assistantMessages === persistedUsage.assistantMessages &&
|
|
104
|
+
liveUsage.total >= persistedUsage.total))
|
|
95
105
|
? liveUsage
|
|
96
|
-
:
|
|
106
|
+
: persistedUsage ||
|
|
107
|
+
(liveUsage.assistantMessages > 0 ? liveUsage : undefined)
|
|
97
108
|
const compactTitle = resolveCompactTitle(sessionID, session?.lastAppliedTitle)
|
|
98
109
|
|
|
99
110
|
if (!enabled) {
|
|
@@ -154,8 +165,8 @@ function useSidebarPanelData(api: TuiPluginApi, sessionID: () => string) {
|
|
|
154
165
|
}
|
|
155
166
|
|
|
156
167
|
const scheduleRefresh = () => {
|
|
157
|
-
queueRefresh(
|
|
158
|
-
queueRefresh(
|
|
168
|
+
queueRefresh(150)
|
|
169
|
+
queueRefresh(600)
|
|
159
170
|
}
|
|
160
171
|
|
|
161
172
|
// Bulk session sync populates messages asynchronously without emitting the
|
package/dist/tui_helpers.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { fitLine, renderSidebarQuotaLineGroups } from './format.js';
|
|
2
2
|
import { collapseQuotaSnapshots } from './quota_render.js';
|
|
3
|
+
import { isSupportedQuotaSnapshot, isSupportedQuotaTitleLabel, } from './supported_quota.js';
|
|
3
4
|
const VISIBLE_QUOTA_STATUSES = new Set([
|
|
4
5
|
'ok',
|
|
5
6
|
'error',
|
|
@@ -70,7 +71,7 @@ function fallbackQuotaTone(detail) {
|
|
|
70
71
|
if (/\bB-/.test(safe))
|
|
71
72
|
return 'error';
|
|
72
73
|
const percents = [
|
|
73
|
-
...safe.matchAll(/\b(?:\d+[hdw]|[DWM]|S7d|O7d|OA7d|Co7d)(\d{1,3})\b/gi),
|
|
74
|
+
...safe.matchAll(/\b(?:\d+[hdw]|[DWM]|S7d|O7d|OA7d|Co7d|Sk5h|SkW)(\d{1,3})\b/gi),
|
|
74
75
|
]
|
|
75
76
|
.map((match) => Number(match[1]))
|
|
76
77
|
.filter((value) => Number.isFinite(value));
|
|
@@ -107,7 +108,7 @@ export function renderSidebarQuotaGroups(quotas, config) {
|
|
|
107
108
|
});
|
|
108
109
|
}
|
|
109
110
|
export function sidebarPanelQuotaSnapshots(panel) {
|
|
110
|
-
return panel?.panelQuotas || panel?.quotas || [];
|
|
111
|
+
return (panel?.panelQuotas || panel?.quotas || []).filter((quota) => isSupportedQuotaSnapshot(quota));
|
|
111
112
|
}
|
|
112
113
|
export function fallbackQuotaGroupsFromTitle(title, width) {
|
|
113
114
|
const parts = (title || '')
|
|
@@ -120,18 +121,22 @@ export function fallbackQuotaGroupsFromTitle(title, width) {
|
|
|
120
121
|
if (quotaParts.length === 0)
|
|
121
122
|
return [];
|
|
122
123
|
const contentWidth = quotaParts.length > 1 ? Math.max(1, width - 2) : width;
|
|
123
|
-
|
|
124
|
+
const groups = [];
|
|
125
|
+
for (const [index, part] of quotaParts.entries()) {
|
|
124
126
|
const line = fitLine(part, contentWidth);
|
|
125
127
|
const parsed = parseQuotaLineParts([line]);
|
|
126
|
-
|
|
128
|
+
if (!isSupportedQuotaTitleLabel(parsed.shortLabel))
|
|
129
|
+
continue;
|
|
130
|
+
groups.push({
|
|
127
131
|
providerID: `fallback:${index}`,
|
|
128
132
|
status: 'ok',
|
|
129
133
|
tone: fallbackQuotaTone(parsed.detail),
|
|
130
134
|
shortLabel: parsed.shortLabel,
|
|
131
135
|
detail: parsed.detail,
|
|
132
136
|
continuationLines: parsed.continuationLines,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return groups;
|
|
135
140
|
}
|
|
136
141
|
export function quotaGroupsUseBullets(groups) {
|
|
137
142
|
return groups.length > 1;
|
package/dist/types.d.ts
CHANGED
|
@@ -12,6 +12,12 @@ export type QuotaWindow = {
|
|
|
12
12
|
usedPercent?: number;
|
|
13
13
|
resetAt?: string;
|
|
14
14
|
};
|
|
15
|
+
export type QuotaStaleReasonKind = 'timeout' | 'network' | 'http_5xx' | 'http_transient' | 'invalid_response' | 'unknown';
|
|
16
|
+
export type QuotaStaleMeta = {
|
|
17
|
+
staleAt: number;
|
|
18
|
+
staleReason: string;
|
|
19
|
+
staleReasonKind: QuotaStaleReasonKind;
|
|
20
|
+
};
|
|
15
21
|
export type QuotaSnapshot = {
|
|
16
22
|
providerID: string;
|
|
17
23
|
/** Adapter ID that produced this snapshot (e.g. openai, rightcode). */
|
|
@@ -35,6 +41,8 @@ export type QuotaSnapshot = {
|
|
|
35
41
|
note?: string;
|
|
36
42
|
/** Multi-window quota (e.g. OpenAI short-term + weekly). */
|
|
37
43
|
windows?: QuotaWindow[];
|
|
44
|
+
/** Last successful snapshot reused during a transient quota fetch failure. */
|
|
45
|
+
stale?: QuotaStaleMeta;
|
|
38
46
|
};
|
|
39
47
|
export type QuotaProviderConfig = {
|
|
40
48
|
enabled?: boolean;
|
package/dist/usage.d.ts
CHANGED
|
@@ -46,12 +46,30 @@ export type UsageOptions = {
|
|
|
46
46
|
export declare function getCacheCoverageMetrics(usage: Pick<UsageSummary, 'input' | 'cacheRead' | 'cacheWrite' | 'assistantMessages' | 'cacheBuckets'>): CacheCoverageMetrics;
|
|
47
47
|
export declare function getProviderCacheCoverageMetrics(usage: Pick<ProviderUsage, 'input' | 'cacheRead' | 'cacheWrite' | 'assistantMessages' | 'cacheBuckets'>): CacheCoverageMetrics;
|
|
48
48
|
export declare function emptyUsageSummary(): UsageSummary;
|
|
49
|
+
export declare function accumulateMessagesInCompletedRange(target: UsageSummary, entries: Array<{
|
|
50
|
+
info: Message;
|
|
51
|
+
}>, startAt?: number, endAt?: number, options?: UsageOptions): UsageSummary;
|
|
49
52
|
export declare function summarizeMessages(entries: Array<{
|
|
50
53
|
info: Message;
|
|
51
54
|
}>, startAt?: number, sessionCount?: number, options?: UsageOptions): UsageSummary;
|
|
52
55
|
export declare function summarizeMessagesInCompletedRange(entries: Array<{
|
|
53
56
|
info: Message;
|
|
54
57
|
}>, startAt: number, endAt: number, sessionCount?: number, options?: UsageOptions): UsageSummary;
|
|
58
|
+
export declare function summarizeMessagesAcrossCompletedRanges(entries: Array<{
|
|
59
|
+
info: Message;
|
|
60
|
+
}>, ranges: Array<{
|
|
61
|
+
startAt: number;
|
|
62
|
+
endAt: number;
|
|
63
|
+
}>, options?: UsageOptions): UsageSummary[];
|
|
64
|
+
export declare function accumulateMessagesAcrossCompletedRanges(summaries: UsageSummary[], entries: Array<{
|
|
65
|
+
info: Message;
|
|
66
|
+
}>, ranges: Array<{
|
|
67
|
+
startAt: number;
|
|
68
|
+
endAt: number;
|
|
69
|
+
}>, options?: UsageOptions): Set<number>;
|
|
70
|
+
export declare function mergeCursorFromEntries(cursor: IncrementalCursor | undefined, entries: Array<{
|
|
71
|
+
info: Message;
|
|
72
|
+
}>): IncrementalCursor | undefined;
|
|
55
73
|
/**
|
|
56
74
|
* P1: Incremental usage aggregation.
|
|
57
75
|
* Only processes messages newer than the cursor. Returns updated cursor.
|
package/dist/usage.js
CHANGED
|
@@ -235,27 +235,111 @@ function isCompletedAssistantInRange(message, startAt = 0, endAt = Number.POSITI
|
|
|
235
235
|
const completed = completedTimeOf(message);
|
|
236
236
|
if (completed === undefined)
|
|
237
237
|
return false;
|
|
238
|
-
return completed >= startAt && completed
|
|
238
|
+
return completed >= startAt && completed < endAt;
|
|
239
239
|
}
|
|
240
|
-
export function
|
|
241
|
-
const summary = emptyUsageSummary();
|
|
242
|
-
summary.sessionCount = sessionCount;
|
|
240
|
+
export function accumulateMessagesInCompletedRange(target, entries, startAt = 0, endAt = Number.POSITIVE_INFINITY, options) {
|
|
243
241
|
for (const entry of entries) {
|
|
244
|
-
if (!isCompletedAssistantInRange(entry.info, startAt))
|
|
242
|
+
if (!isCompletedAssistantInRange(entry.info, startAt, endAt))
|
|
245
243
|
continue;
|
|
246
|
-
addMessageUsage(
|
|
244
|
+
addMessageUsage(target, entry.info, options);
|
|
247
245
|
}
|
|
246
|
+
return target;
|
|
247
|
+
}
|
|
248
|
+
export function summarizeMessages(entries, startAt = 0, sessionCount = 1, options) {
|
|
249
|
+
const summary = emptyUsageSummary();
|
|
250
|
+
summary.sessionCount = sessionCount;
|
|
251
|
+
accumulateMessagesInCompletedRange(summary, entries, startAt, Infinity, options);
|
|
248
252
|
return summary;
|
|
249
253
|
}
|
|
250
254
|
export function summarizeMessagesInCompletedRange(entries, startAt, endAt, sessionCount = 1, options) {
|
|
251
255
|
const summary = emptyUsageSummary();
|
|
252
256
|
summary.sessionCount = sessionCount;
|
|
257
|
+
accumulateMessagesInCompletedRange(summary, entries, startAt, endAt, options);
|
|
258
|
+
return summary;
|
|
259
|
+
}
|
|
260
|
+
function rangeIndexForCompletedAt(ranges, completedAt) {
|
|
261
|
+
let low = 0;
|
|
262
|
+
let high = ranges.length - 1;
|
|
263
|
+
while (low <= high) {
|
|
264
|
+
const mid = Math.floor((low + high) / 2);
|
|
265
|
+
const range = ranges[mid];
|
|
266
|
+
if (completedAt < range.startAt) {
|
|
267
|
+
high = mid - 1;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (completedAt >= range.endAt) {
|
|
271
|
+
low = mid + 1;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
return mid;
|
|
275
|
+
}
|
|
276
|
+
return -1;
|
|
277
|
+
}
|
|
278
|
+
export function summarizeMessagesAcrossCompletedRanges(entries, ranges, options) {
|
|
279
|
+
const summaries = ranges.map(() => emptyUsageSummary());
|
|
280
|
+
accumulateMessagesAcrossCompletedRanges(summaries, entries, ranges, options);
|
|
281
|
+
return summaries;
|
|
282
|
+
}
|
|
283
|
+
export function accumulateMessagesAcrossCompletedRanges(summaries, entries, ranges, options) {
|
|
284
|
+
const touched = new Set();
|
|
285
|
+
if (ranges.length === 0)
|
|
286
|
+
return touched;
|
|
253
287
|
for (const entry of entries) {
|
|
254
|
-
if (!
|
|
288
|
+
if (!isAssistant(entry.info))
|
|
289
|
+
continue;
|
|
290
|
+
const completed = completedTimeOf(entry.info);
|
|
291
|
+
if (completed === undefined)
|
|
255
292
|
continue;
|
|
256
|
-
|
|
293
|
+
const index = rangeIndexForCompletedAt(ranges, completed);
|
|
294
|
+
if (index < 0)
|
|
295
|
+
continue;
|
|
296
|
+
addMessageUsage(summaries[index], entry.info, options);
|
|
297
|
+
touched.add(index);
|
|
257
298
|
}
|
|
258
|
-
|
|
299
|
+
for (const index of touched) {
|
|
300
|
+
summaries[index].sessionCount = 1;
|
|
301
|
+
}
|
|
302
|
+
return touched;
|
|
303
|
+
}
|
|
304
|
+
export function mergeCursorFromEntries(cursor, entries) {
|
|
305
|
+
let bestTime = typeof cursor?.lastMessageTime === 'number' &&
|
|
306
|
+
Number.isFinite(cursor.lastMessageTime)
|
|
307
|
+
? cursor.lastMessageTime
|
|
308
|
+
: Number.NEGATIVE_INFINITY;
|
|
309
|
+
let bestID = cursor?.lastMessageId || '';
|
|
310
|
+
const idsAtBestTime = new Set(Array.isArray(cursor?.lastMessageIdsAtTime)
|
|
311
|
+
? cursor.lastMessageIdsAtTime
|
|
312
|
+
: cursor?.lastMessageId && Number.isFinite(bestTime)
|
|
313
|
+
? [cursor.lastMessageId]
|
|
314
|
+
: []);
|
|
315
|
+
for (const entry of entries) {
|
|
316
|
+
const msg = entry.info;
|
|
317
|
+
if (!isAssistant(msg))
|
|
318
|
+
continue;
|
|
319
|
+
const completed = completedTimeOf(msg);
|
|
320
|
+
if (completed === undefined)
|
|
321
|
+
continue;
|
|
322
|
+
if (completed > bestTime) {
|
|
323
|
+
bestTime = completed;
|
|
324
|
+
bestID = msg.id;
|
|
325
|
+
idsAtBestTime.clear();
|
|
326
|
+
idsAtBestTime.add(msg.id);
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (completed !== bestTime)
|
|
330
|
+
continue;
|
|
331
|
+
idsAtBestTime.add(msg.id);
|
|
332
|
+
if (msg.id.localeCompare(bestID) > 0) {
|
|
333
|
+
bestID = msg.id;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (!Number.isFinite(bestTime) || !bestID)
|
|
337
|
+
return undefined;
|
|
338
|
+
return {
|
|
339
|
+
lastMessageId: bestID,
|
|
340
|
+
lastMessageTime: bestTime,
|
|
341
|
+
lastMessageIdsAtTime: Array.from(idsAtBestTime).sort(),
|
|
342
|
+
};
|
|
259
343
|
}
|
|
260
344
|
/**
|
|
261
345
|
* P1: Incremental usage aggregation.
|
package/dist/usage_service.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { PluginInput } from '@opencode-ai/plugin';
|
|
2
|
+
import { type HistoryPeriod } from './period.js';
|
|
2
3
|
import { type UsageSummary } from './usage.js';
|
|
4
|
+
export type { HistoryUsageRow, HistoryUsageResult } from './history_usage.js';
|
|
5
|
+
import type { HistoryUsageResult } from './history_usage.js';
|
|
3
6
|
import type { QuotaSidebarConfig, QuotaSidebarState } from './types.js';
|
|
4
7
|
type DescendantsResolver = {
|
|
5
8
|
listDescendantSessionIDs: (sessionID: string, opts: {
|
|
@@ -24,8 +27,8 @@ export declare function createUsageService(deps: {
|
|
|
24
27
|
}): {
|
|
25
28
|
summarizeSessionUsageForDisplay: (sessionID: string, includeChildren: boolean) => Promise<UsageSummary>;
|
|
26
29
|
summarizeForTool: (period: "session" | "day" | "week" | "month", sessionID: string, includeChildren: boolean) => Promise<UsageSummary>;
|
|
30
|
+
summarizeHistoryUsage: (period: HistoryPeriod, rawSince: string) => Promise<HistoryUsageResult>;
|
|
27
31
|
markSessionDirty: (sessionID: string) => void;
|
|
28
32
|
markForceRescan: (sessionID: string) => void;
|
|
29
33
|
forgetSession: (sessionID: string) => void;
|
|
30
34
|
};
|
|
31
|
-
export {};
|