@leo000001/opencode-quota-sidebar 3.0.10 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +0 -1
- package/README.md +157 -42
- package/README.zh-CN.md +157 -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 +302 -41
- 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 +18 -8
- 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/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 {};
|