@leo000001/opencode-quota-sidebar 4.0.5 → 4.0.11
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 +504 -446
- package/README.zh-CN.md +516 -458
- package/dist/cli.d.ts +38 -0
- package/dist/cli.js +153 -42
- package/dist/cli_render.d.ts +4 -4
- package/dist/cli_render.js +74 -74
- package/dist/cost.d.ts +21 -4
- package/dist/cost.js +493 -264
- package/dist/format.d.ts +5 -5
- package/dist/format.js +288 -287
- package/dist/history_usage.d.ts +15 -9
- package/dist/history_usage.js +28 -22
- package/dist/index.d.ts +3 -3
- package/dist/index.js +35 -34
- package/dist/models_dev_pricing.d.ts +6 -0
- package/dist/models_dev_pricing.js +226 -0
- package/dist/opencode_pricing.d.ts +14 -0
- package/dist/opencode_pricing.js +273 -0
- package/dist/storage.d.ts +3 -3
- package/dist/storage.js +27 -28
- package/dist/storage_parse.d.ts +1 -1
- package/dist/storage_parse.js +51 -45
- package/dist/storage_paths.d.ts +1 -0
- package/dist/storage_paths.js +26 -11
- package/dist/title_apply.d.ts +5 -22
- package/dist/title_apply.js +19 -61
- package/dist/tui.d.ts +1 -1
- package/dist/tui.tsx +481 -471
- package/dist/tui_helpers.d.ts +5 -3
- package/dist/tui_helpers.js +62 -34
- package/dist/types.d.ts +8 -10
- package/dist/usage.d.ts +9 -6
- package/dist/usage.js +27 -21
- package/dist/usage_service.d.ts +8 -7
- package/dist/usage_service.js +261 -150
- package/package.json +1 -1
package/dist/tui_helpers.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { QuotaSidebarConfig, QuotaSnapshot, SidebarPanelState } from
|
|
2
|
-
|
|
1
|
+
import type { QuotaSidebarConfig, QuotaSnapshot, SidebarPanelState } from "./types.js";
|
|
2
|
+
import type { UsageSummary } from "./usage.js";
|
|
3
|
+
export type SidebarQuotaTone = "success" | "warning" | "error" | "muted";
|
|
3
4
|
export type SidebarQuotaGroup = {
|
|
4
5
|
providerID: string;
|
|
5
|
-
status: QuotaSnapshot[
|
|
6
|
+
status: QuotaSnapshot["status"];
|
|
6
7
|
tone: SidebarQuotaTone;
|
|
7
8
|
shortLabel: string;
|
|
8
9
|
detail: string;
|
|
@@ -10,6 +11,7 @@ export type SidebarQuotaGroup = {
|
|
|
10
11
|
};
|
|
11
12
|
export declare function renderSidebarQuotaGroups(quotas: QuotaSnapshot[], config: QuotaSidebarConfig): SidebarQuotaGroup[];
|
|
12
13
|
export declare function sidebarPanelQuotaSnapshots(panel?: SidebarPanelState): QuotaSnapshot[];
|
|
14
|
+
export declare function mergeLiveAndPersistedPanelUsage(liveUsage: UsageSummary | undefined, persistedUsage: UsageSummary | undefined): UsageSummary | undefined;
|
|
13
15
|
export declare function fallbackQuotaGroupsFromTitle(title: string, width: number): SidebarQuotaGroup[];
|
|
14
16
|
export declare function quotaGroupsUseBullets(groups: SidebarQuotaGroup[]): boolean;
|
|
15
17
|
export declare function quotaGroupsAreCollapsible(groups: SidebarQuotaGroup[]): boolean;
|
package/dist/tui_helpers.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { fitLine, renderSidebarQuotaLineGroups } from
|
|
2
|
-
import { collapseQuotaSnapshots } from
|
|
3
|
-
import { isSupportedQuotaSnapshot, isSupportedQuotaTitleLabel, } from
|
|
1
|
+
import { fitLine, renderSidebarQuotaLineGroups } from "./format.js";
|
|
2
|
+
import { collapseQuotaSnapshots } from "./quota_render.js";
|
|
3
|
+
import { isSupportedQuotaSnapshot, isSupportedQuotaTitleLabel, } from "./supported_quota.js";
|
|
4
4
|
const VISIBLE_QUOTA_STATUSES = new Set([
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
"ok",
|
|
6
|
+
"error",
|
|
7
|
+
"unsupported",
|
|
8
|
+
"unavailable",
|
|
9
9
|
]);
|
|
10
10
|
function parseQuotaLineParts(lines) {
|
|
11
|
-
const firstLine = lines[0]?.trimStart() ||
|
|
11
|
+
const firstLine = lines[0]?.trimStart() || "";
|
|
12
12
|
const match = /^(\S+)(?:\s+(.*))?$/.exec(firstLine);
|
|
13
|
-
const shortLabel = match?.[1] || firstLine ||
|
|
14
|
-
const detail = match?.[2] ||
|
|
13
|
+
const shortLabel = match?.[1] || firstLine || "Quota";
|
|
14
|
+
const detail = match?.[2] || "";
|
|
15
15
|
const continuationLines = lines
|
|
16
16
|
.slice(1)
|
|
17
17
|
.map((line) => line.trimEnd())
|
|
@@ -37,52 +37,52 @@ function quotaPercents(quota) {
|
|
|
37
37
|
return values;
|
|
38
38
|
}
|
|
39
39
|
function quotaTone(quota) {
|
|
40
|
-
if (quota.status ===
|
|
41
|
-
return
|
|
42
|
-
if (quota.status ===
|
|
43
|
-
return
|
|
40
|
+
if (quota.status === "error")
|
|
41
|
+
return "error";
|
|
42
|
+
if (quota.status === "unsupported" || quota.status === "unavailable") {
|
|
43
|
+
return "muted";
|
|
44
44
|
}
|
|
45
|
-
if (quota.status !==
|
|
46
|
-
return
|
|
45
|
+
if (quota.status !== "ok")
|
|
46
|
+
return "muted";
|
|
47
47
|
const percents = quotaPercents(quota);
|
|
48
48
|
if (percents.length === 0) {
|
|
49
49
|
if (quota.balance && Number.isFinite(quota.balance.amount)) {
|
|
50
50
|
if (quota.balance.amount < 0)
|
|
51
|
-
return
|
|
52
|
-
return
|
|
51
|
+
return "error";
|
|
52
|
+
return "muted";
|
|
53
53
|
}
|
|
54
|
-
return
|
|
54
|
+
return "muted";
|
|
55
55
|
}
|
|
56
56
|
const remaining = Math.min(...percents);
|
|
57
57
|
if (remaining <= 5)
|
|
58
|
-
return
|
|
58
|
+
return "error";
|
|
59
59
|
if (remaining <= 20)
|
|
60
|
-
return
|
|
61
|
-
return
|
|
60
|
+
return "warning";
|
|
61
|
+
return "success";
|
|
62
62
|
}
|
|
63
63
|
function fallbackQuotaTone(detail) {
|
|
64
64
|
const safe = detail.trim();
|
|
65
65
|
if (!safe)
|
|
66
|
-
return
|
|
66
|
+
return "muted";
|
|
67
67
|
if (/\b(?:unsupported|unavailable)\b/i.test(safe))
|
|
68
|
-
return
|
|
68
|
+
return "muted";
|
|
69
69
|
if (/\berror\b/i.test(safe) || /^\?$/.test(safe))
|
|
70
|
-
return
|
|
70
|
+
return "error";
|
|
71
71
|
if (/\bB-/.test(safe))
|
|
72
|
-
return
|
|
72
|
+
return "error";
|
|
73
73
|
const percents = [
|
|
74
74
|
...safe.matchAll(/\b(?:\d+[hdw]|[DWM]|S7d|O7d|OA7d|Co7d|Sk5h|SkW)(\d{1,3})\b/gi),
|
|
75
75
|
]
|
|
76
76
|
.map((match) => Number(match[1]))
|
|
77
77
|
.filter((value) => Number.isFinite(value));
|
|
78
78
|
if (percents.length === 0)
|
|
79
|
-
return
|
|
79
|
+
return "muted";
|
|
80
80
|
const remaining = Math.min(...percents);
|
|
81
81
|
if (remaining <= 5)
|
|
82
|
-
return
|
|
82
|
+
return "error";
|
|
83
83
|
if (remaining <= 20)
|
|
84
|
-
return
|
|
85
|
-
return
|
|
84
|
+
return "warning";
|
|
85
|
+
return "success";
|
|
86
86
|
}
|
|
87
87
|
export function renderSidebarQuotaGroups(quotas, config) {
|
|
88
88
|
const visibleQuotaCount = collapseQuotaSnapshots(quotas).filter((quota) => VISIBLE_QUOTA_STATUSES.has(quota.status)).length;
|
|
@@ -110,14 +110,42 @@ export function renderSidebarQuotaGroups(quotas, config) {
|
|
|
110
110
|
export function sidebarPanelQuotaSnapshots(panel) {
|
|
111
111
|
return (panel?.panelQuotas || panel?.quotas || []).filter((quota) => isSupportedQuotaSnapshot(quota));
|
|
112
112
|
}
|
|
113
|
+
export function mergeLiveAndPersistedPanelUsage(liveUsage, persistedUsage) {
|
|
114
|
+
if (!liveUsage)
|
|
115
|
+
return persistedUsage;
|
|
116
|
+
if (!persistedUsage)
|
|
117
|
+
return liveUsage;
|
|
118
|
+
const preferLive = liveUsage.assistantMessages > 0 &&
|
|
119
|
+
(liveUsage.assistantMessages > persistedUsage.assistantMessages ||
|
|
120
|
+
(liveUsage.assistantMessages === persistedUsage.assistantMessages &&
|
|
121
|
+
liveUsage.total >= persistedUsage.total));
|
|
122
|
+
if (!preferLive)
|
|
123
|
+
return persistedUsage;
|
|
124
|
+
const sameAggregateSurface = liveUsage.assistantMessages === persistedUsage.assistantMessages &&
|
|
125
|
+
liveUsage.input === persistedUsage.input &&
|
|
126
|
+
liveUsage.output === persistedUsage.output &&
|
|
127
|
+
liveUsage.cacheRead === persistedUsage.cacheRead &&
|
|
128
|
+
liveUsage.cacheWrite === persistedUsage.cacheWrite &&
|
|
129
|
+
liveUsage.total === persistedUsage.total;
|
|
130
|
+
if (!sameAggregateSurface ||
|
|
131
|
+
liveUsage.apiCost > 0 ||
|
|
132
|
+
persistedUsage.apiCost <= 0) {
|
|
133
|
+
return liveUsage;
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
...liveUsage,
|
|
137
|
+
apiCost: persistedUsage.apiCost,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
113
140
|
export function fallbackQuotaGroupsFromTitle(title, width) {
|
|
114
|
-
|
|
115
|
-
|
|
141
|
+
// Legacy compatibility: old sessions may only have compact title fragments.
|
|
142
|
+
const parts = (title || "")
|
|
143
|
+
.split(" | ")
|
|
116
144
|
.map((part) => part.trim())
|
|
117
145
|
.filter(Boolean);
|
|
118
146
|
const quotaParts = parts
|
|
119
147
|
.slice(1)
|
|
120
|
-
.filter((part) => !/^Cd\d/.test(part) && !/^Est\b/.test(part));
|
|
148
|
+
.filter((part) => !/^Cd\d/.test(part) && !/^API\b/.test(part) && !/^Est\b/.test(part));
|
|
121
149
|
if (quotaParts.length === 0)
|
|
122
150
|
return [];
|
|
123
151
|
const contentWidth = quotaParts.length > 1 ? Math.max(1, width - 2) : width;
|
|
@@ -129,7 +157,7 @@ export function fallbackQuotaGroupsFromTitle(title, width) {
|
|
|
129
157
|
continue;
|
|
130
158
|
groups.push({
|
|
131
159
|
providerID: `fallback:${index}`,
|
|
132
|
-
status:
|
|
160
|
+
status: "ok",
|
|
133
161
|
tone: fallbackQuotaTone(parsed.detail),
|
|
134
162
|
shortLabel: parsed.shortLabel,
|
|
135
163
|
detail: parsed.detail,
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type QuotaStatus =
|
|
2
|
-
export type SidebarTitleMode =
|
|
1
|
+
export type QuotaStatus = "ok" | "unavailable" | "unsupported" | "error";
|
|
2
|
+
export type SidebarTitleMode = "auto" | "multiline" | "compact";
|
|
3
3
|
export type QuotaWindow = {
|
|
4
4
|
label: string;
|
|
5
5
|
/** Set false when this window line should not render a trailing percentage. */
|
|
@@ -12,7 +12,7 @@ export type QuotaWindow = {
|
|
|
12
12
|
usedPercent?: number;
|
|
13
13
|
resetAt?: string;
|
|
14
14
|
};
|
|
15
|
-
export type QuotaStaleReasonKind =
|
|
15
|
+
export type QuotaStaleReasonKind = "timeout" | "network" | "http_5xx" | "http_transient" | "invalid_response" | "unknown";
|
|
16
16
|
export type QuotaStaleMeta = {
|
|
17
17
|
staleAt: number;
|
|
18
18
|
staleReason: string;
|
|
@@ -52,7 +52,7 @@ export type SessionTitleState = {
|
|
|
52
52
|
baseTitle: string;
|
|
53
53
|
lastAppliedTitle?: string;
|
|
54
54
|
};
|
|
55
|
-
export type CacheCoverageMode =
|
|
55
|
+
export type CacheCoverageMode = "none" | "read-only" | "read-write";
|
|
56
56
|
export type CacheUsageBucket = {
|
|
57
57
|
input: number;
|
|
58
58
|
cacheRead: number;
|
|
@@ -95,6 +95,10 @@ export type CachedProviderUsage = {
|
|
|
95
95
|
export type CachedSessionUsage = {
|
|
96
96
|
/** Billing aggregation cache version for cost/apiCost refresh migrations. */
|
|
97
97
|
billingVersion?: number;
|
|
98
|
+
/** Pricing fingerprint for the exact model/rate set used by this session. */
|
|
99
|
+
pricingFingerprint?: string;
|
|
100
|
+
/** Unique assistant-message provider/model pairs used to build the fingerprint. */
|
|
101
|
+
pricingKeys?: string[];
|
|
98
102
|
input: number;
|
|
99
103
|
output: number;
|
|
100
104
|
reasoning: number;
|
|
@@ -172,12 +176,6 @@ export type QuotaSidebarConfig = {
|
|
|
172
176
|
* sidebar plugin render the rich panel layout.
|
|
173
177
|
*/
|
|
174
178
|
titleMode?: SidebarTitleMode;
|
|
175
|
-
/**
|
|
176
|
-
* Legacy switch retained for compatibility.
|
|
177
|
-
* TUI keeps a compact multiline sidebar layout; Desktop keeps a compact
|
|
178
|
-
* single-line layout.
|
|
179
|
-
*/
|
|
180
|
-
multilineTitle?: boolean;
|
|
181
179
|
showCost: boolean;
|
|
182
180
|
showQuota: boolean;
|
|
183
181
|
/** When true, wrap long quota lines and indent continuations. */
|
package/dist/usage.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type { AssistantMessage, Message } from
|
|
2
|
-
import type { CacheCoverageMetrics, CacheCoverageMode, CacheUsageBuckets, CachedSessionUsage, IncrementalCursor, RecentProviderEvent } from
|
|
1
|
+
import type { AssistantMessage, Message } from "@opencode-ai/sdk";
|
|
2
|
+
import type { CacheCoverageMetrics, CacheCoverageMode, CacheUsageBuckets, CachedSessionUsage, IncrementalCursor, RecentProviderEvent } from "./types.js";
|
|
3
3
|
/**
|
|
4
4
|
* Billing cache version — bump this whenever the persisted `CachedSessionUsage`
|
|
5
5
|
* shape changes in a way that requires recomputation (e.g. new aggregate
|
|
6
6
|
* fields). This is distinct from the plugin *state* version managed by the
|
|
7
7
|
* persistence layer; billing version only governs usage-cache staleness.
|
|
8
8
|
*/
|
|
9
|
-
export declare const USAGE_BILLING_CACHE_VERSION =
|
|
9
|
+
export declare const USAGE_BILLING_CACHE_VERSION = 10;
|
|
10
10
|
export type ProviderUsage = {
|
|
11
11
|
providerID: string;
|
|
12
12
|
input: number;
|
|
@@ -43,8 +43,8 @@ export type UsageOptions = {
|
|
|
43
43
|
/** Cache-behavior classifier for the message model/provider. */
|
|
44
44
|
classifyCacheMode?: (message: AssistantMessage) => CacheCoverageMode;
|
|
45
45
|
};
|
|
46
|
-
export declare function getCacheCoverageMetrics(usage: Pick<UsageSummary,
|
|
47
|
-
export declare function getProviderCacheCoverageMetrics(usage: Pick<ProviderUsage,
|
|
46
|
+
export declare function getCacheCoverageMetrics(usage: Pick<UsageSummary, "input" | "cacheRead" | "cacheWrite" | "assistantMessages" | "cacheBuckets">): CacheCoverageMetrics;
|
|
47
|
+
export declare function getProviderCacheCoverageMetrics(usage: Pick<ProviderUsage, "input" | "cacheRead" | "cacheWrite" | "assistantMessages" | "cacheBuckets">): CacheCoverageMetrics;
|
|
48
48
|
export declare function emptyUsageSummary(): UsageSummary;
|
|
49
49
|
export declare function accumulateMessagesInCompletedRange(target: UsageSummary, entries: Array<{
|
|
50
50
|
info: Message;
|
|
@@ -84,5 +84,8 @@ export declare function summarizeMessagesIncremental(entries: Array<{
|
|
|
84
84
|
export declare function mergeUsage(target: UsageSummary, source: UsageSummary, options?: {
|
|
85
85
|
includeCost?: boolean;
|
|
86
86
|
}): UsageSummary;
|
|
87
|
-
export declare function toCachedSessionUsage(summary: UsageSummary
|
|
87
|
+
export declare function toCachedSessionUsage(summary: UsageSummary, options?: {
|
|
88
|
+
pricingFingerprint?: string;
|
|
89
|
+
pricingKeys?: string[];
|
|
90
|
+
}): CachedSessionUsage;
|
|
88
91
|
export declare function fromCachedSessionUsage(cached: CachedSessionUsage, sessionCount?: number): UsageSummary;
|
package/dist/usage.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* fields). This is distinct from the plugin *state* version managed by the
|
|
5
5
|
* persistence layer; billing version only governs usage-cache staleness.
|
|
6
6
|
*/
|
|
7
|
-
export const USAGE_BILLING_CACHE_VERSION =
|
|
7
|
+
export const USAGE_BILLING_CACHE_VERSION = 10;
|
|
8
8
|
const MAX_RECENT_PROVIDER_EVENTS = 100;
|
|
9
9
|
function emptyCacheUsageBucket() {
|
|
10
10
|
return {
|
|
@@ -150,7 +150,7 @@ function emptyProviderUsage(providerID) {
|
|
|
150
150
|
};
|
|
151
151
|
}
|
|
152
152
|
function isAssistant(message) {
|
|
153
|
-
return message.role ===
|
|
153
|
+
return message.role === "assistant";
|
|
154
154
|
}
|
|
155
155
|
function tokenTotal(message) {
|
|
156
156
|
return (message.tokens.input +
|
|
@@ -166,8 +166,8 @@ function mergedOutput(message) {
|
|
|
166
166
|
function mergeRecentProviderEvents(target, source) {
|
|
167
167
|
const merged = [...(target || []), ...(source || [])]
|
|
168
168
|
.filter((item) => !!item &&
|
|
169
|
-
typeof item.providerID ===
|
|
170
|
-
typeof item.completedAt ===
|
|
169
|
+
typeof item.providerID === "string" &&
|
|
170
|
+
typeof item.completedAt === "number" &&
|
|
171
171
|
Number.isFinite(item.completedAt))
|
|
172
172
|
.sort((left, right) => right.completedAt - left.completedAt);
|
|
173
173
|
return merged.length > MAX_RECENT_PROVIDER_EVENTS
|
|
@@ -177,7 +177,7 @@ function mergeRecentProviderEvents(target, source) {
|
|
|
177
177
|
function addMessageUsage(target, message, options) {
|
|
178
178
|
const total = tokenTotal(message);
|
|
179
179
|
const output = mergedOutput(message);
|
|
180
|
-
const cost = typeof message.cost ===
|
|
180
|
+
const cost = typeof message.cost === "number" && Number.isFinite(message.cost)
|
|
181
181
|
? message.cost
|
|
182
182
|
: 0;
|
|
183
183
|
const apiCostRaw = options?.calcApiCost ? options.calcApiCost(message) : 0;
|
|
@@ -207,23 +207,25 @@ function addMessageUsage(target, message, options) {
|
|
|
207
207
|
provider.apiCost += apiCost;
|
|
208
208
|
provider.assistantMessages += 1;
|
|
209
209
|
target.providers[message.providerID] = provider;
|
|
210
|
-
const cacheMode = options?.classifyCacheMode?.(message) ||
|
|
211
|
-
if (cacheMode ===
|
|
210
|
+
const cacheMode = options?.classifyCacheMode?.(message) || "none";
|
|
211
|
+
if (cacheMode === "read-only") {
|
|
212
212
|
const buckets = (target.cacheBuckets ||= emptyCacheUsageBuckets());
|
|
213
213
|
addMessageCacheUsage(buckets.readOnly, message);
|
|
214
|
-
const providerBuckets = (provider.cacheBuckets ||=
|
|
214
|
+
const providerBuckets = (provider.cacheBuckets ||=
|
|
215
|
+
emptyCacheUsageBuckets());
|
|
215
216
|
addMessageCacheUsage(providerBuckets.readOnly, message);
|
|
216
217
|
}
|
|
217
|
-
else if (cacheMode ===
|
|
218
|
+
else if (cacheMode === "read-write") {
|
|
218
219
|
const buckets = (target.cacheBuckets ||= emptyCacheUsageBuckets());
|
|
219
220
|
addMessageCacheUsage(buckets.readWrite, message);
|
|
220
|
-
const providerBuckets = (provider.cacheBuckets ||=
|
|
221
|
+
const providerBuckets = (provider.cacheBuckets ||=
|
|
222
|
+
emptyCacheUsageBuckets());
|
|
221
223
|
addMessageCacheUsage(providerBuckets.readWrite, message);
|
|
222
224
|
}
|
|
223
225
|
}
|
|
224
226
|
function completedTimeOf(message) {
|
|
225
227
|
const completed = message.time.completed;
|
|
226
|
-
if (typeof completed !==
|
|
228
|
+
if (typeof completed !== "number")
|
|
227
229
|
return undefined;
|
|
228
230
|
if (!Number.isFinite(completed))
|
|
229
231
|
return undefined;
|
|
@@ -302,11 +304,11 @@ export function accumulateMessagesAcrossCompletedRanges(summaries, entries, rang
|
|
|
302
304
|
return touched;
|
|
303
305
|
}
|
|
304
306
|
export function mergeCursorFromEntries(cursor, entries) {
|
|
305
|
-
let bestTime = typeof cursor?.lastMessageTime ===
|
|
307
|
+
let bestTime = typeof cursor?.lastMessageTime === "number" &&
|
|
306
308
|
Number.isFinite(cursor.lastMessageTime)
|
|
307
309
|
? cursor.lastMessageTime
|
|
308
310
|
: Number.NEGATIVE_INFINITY;
|
|
309
|
-
let bestID = cursor?.lastMessageId ||
|
|
311
|
+
let bestID = cursor?.lastMessageId || "";
|
|
310
312
|
const idsAtBestTime = new Set(Array.isArray(cursor?.lastMessageIdsAtTime)
|
|
311
313
|
? cursor.lastMessageIdsAtTime
|
|
312
314
|
: cursor?.lastMessageId && Number.isFinite(bestTime)
|
|
@@ -350,7 +352,7 @@ export function summarizeMessagesIncremental(entries, existingUsage, cursor, for
|
|
|
350
352
|
// If no cursor or force rescan, do full scan
|
|
351
353
|
if (forceRescan ||
|
|
352
354
|
!cursor?.lastMessageId ||
|
|
353
|
-
typeof cursor.lastMessageTime !==
|
|
355
|
+
typeof cursor.lastMessageTime !== "number" ||
|
|
354
356
|
!Number.isFinite(cursor.lastMessageTime) ||
|
|
355
357
|
!existingUsage) {
|
|
356
358
|
const usage = summarizeMessages(entries, 0, 1, options);
|
|
@@ -382,7 +384,7 @@ export function summarizeMessagesIncremental(entries, existingUsage, cursor, for
|
|
|
382
384
|
const msg = entry.info;
|
|
383
385
|
if (!isAssistant(msg))
|
|
384
386
|
continue;
|
|
385
|
-
if (typeof msg.time.completed !==
|
|
387
|
+
if (typeof msg.time.completed !== "number")
|
|
386
388
|
continue;
|
|
387
389
|
if (!Number.isFinite(msg.time.completed))
|
|
388
390
|
continue;
|
|
@@ -407,7 +409,7 @@ export function summarizeMessagesIncremental(entries, existingUsage, cursor, for
|
|
|
407
409
|
}
|
|
408
410
|
const isAfterCursor = (message) => {
|
|
409
411
|
const completed = message.time.completed;
|
|
410
|
-
if (typeof completed !==
|
|
412
|
+
if (typeof completed !== "number" || !Number.isFinite(completed))
|
|
411
413
|
return false;
|
|
412
414
|
if (completed > cursorTime)
|
|
413
415
|
return true;
|
|
@@ -430,7 +432,7 @@ export function summarizeMessagesIncremental(entries, existingUsage, cursor, for
|
|
|
430
432
|
const msg = entry.info;
|
|
431
433
|
if (!isAssistant(msg))
|
|
432
434
|
continue;
|
|
433
|
-
if (typeof msg.time.completed !==
|
|
435
|
+
if (typeof msg.time.completed !== "number")
|
|
434
436
|
continue;
|
|
435
437
|
if (!Number.isFinite(msg.time.completed))
|
|
436
438
|
continue;
|
|
@@ -487,7 +489,7 @@ function collectCompletedAssistantIdsAt(entries, completedTime) {
|
|
|
487
489
|
const msg = entry.info;
|
|
488
490
|
if (!isAssistant(msg))
|
|
489
491
|
continue;
|
|
490
|
-
if (typeof msg.time.completed !==
|
|
492
|
+
if (typeof msg.time.completed !== "number")
|
|
491
493
|
continue;
|
|
492
494
|
if (!Number.isFinite(msg.time.completed))
|
|
493
495
|
continue;
|
|
@@ -500,12 +502,12 @@ function collectCompletedAssistantIdsAt(entries, completedTime) {
|
|
|
500
502
|
function findLastCompletedAssistant(entries) {
|
|
501
503
|
let best;
|
|
502
504
|
let bestTime = -Infinity;
|
|
503
|
-
let bestID =
|
|
505
|
+
let bestID = "";
|
|
504
506
|
for (const entry of entries) {
|
|
505
507
|
const msg = entry.info;
|
|
506
508
|
if (!isAssistant(msg))
|
|
507
509
|
continue;
|
|
508
|
-
if (typeof msg.time.completed !==
|
|
510
|
+
if (typeof msg.time.completed !== "number")
|
|
509
511
|
continue;
|
|
510
512
|
if (!Number.isFinite(msg.time.completed))
|
|
511
513
|
continue;
|
|
@@ -561,7 +563,7 @@ export function mergeUsage(target, source, options) {
|
|
|
561
563
|
}
|
|
562
564
|
return target;
|
|
563
565
|
}
|
|
564
|
-
export function toCachedSessionUsage(summary) {
|
|
566
|
+
export function toCachedSessionUsage(summary, options) {
|
|
565
567
|
const providers = Object.entries(summary.providers).reduce((acc, [providerID, provider]) => {
|
|
566
568
|
acc[providerID] = {
|
|
567
569
|
input: provider.input,
|
|
@@ -580,6 +582,10 @@ export function toCachedSessionUsage(summary) {
|
|
|
580
582
|
}, {});
|
|
581
583
|
return {
|
|
582
584
|
billingVersion: USAGE_BILLING_CACHE_VERSION,
|
|
585
|
+
pricingFingerprint: options?.pricingFingerprint,
|
|
586
|
+
pricingKeys: options?.pricingKeys
|
|
587
|
+
? Array.from(new Set(options.pricingKeys)).sort()
|
|
588
|
+
: undefined,
|
|
583
589
|
input: summary.input,
|
|
584
590
|
output: summary.output,
|
|
585
591
|
// Always 0 after merge into output; kept for serialization shape.
|
package/dist/usage_service.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { PluginInput } from
|
|
2
|
-
import { type HistoryPeriod } from
|
|
3
|
-
import { type UsageSummary } from
|
|
4
|
-
export type { HistoryUsageRow, HistoryUsageResult } from
|
|
5
|
-
import type { HistoryUsageResult } from
|
|
6
|
-
import type { QuotaSidebarConfig, QuotaSidebarState } from
|
|
1
|
+
import type { PluginInput } from "@opencode-ai/plugin";
|
|
2
|
+
import { type HistoryPeriod } from "./period.js";
|
|
3
|
+
import { type UsageSummary } from "./usage.js";
|
|
4
|
+
export type { HistoryUsageRow, HistoryUsageResult } from "./history_usage.js";
|
|
5
|
+
import type { HistoryUsageResult } from "./history_usage.js";
|
|
6
|
+
import type { QuotaSidebarConfig, QuotaSidebarState } from "./types.js";
|
|
7
7
|
type DescendantsResolver = {
|
|
8
8
|
listDescendantSessionIDs: (sessionID: string, opts: {
|
|
9
9
|
maxDepth: number;
|
|
@@ -20,8 +20,9 @@ export declare function createUsageService(deps: {
|
|
|
20
20
|
state: QuotaSidebarState;
|
|
21
21
|
config: QuotaSidebarConfig;
|
|
22
22
|
statePath: string;
|
|
23
|
-
client: PluginInput[
|
|
23
|
+
client: PluginInput["client"];
|
|
24
24
|
directory: string;
|
|
25
|
+
worktree?: string;
|
|
25
26
|
persistence: Persistence;
|
|
26
27
|
descendantsResolver: DescendantsResolver;
|
|
27
28
|
}): {
|