@leo000001/opencode-quota-sidebar 1.13.3 → 1.13.6
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 +4 -0
- package/README.md +10 -0
- package/dist/cost.d.ts +6 -0
- package/dist/cost.js +22 -5
- package/dist/providers/third_party/buzz.js +1 -1
- package/dist/usage.d.ts +4 -2
- package/dist/usage.js +9 -4
- package/dist/usage_service.js +6 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
- Add Buzz API balance support for OpenAI-compatible providers that use a Buzz `baseURL`.
|
|
6
6
|
- Document Buzz configuration, rendering, and outbound billing endpoints.
|
|
7
|
+
- Keep session measured cost aligned with OpenCode root-session `message.cost` while still including descendant subagent usage in API-equivalent cost.
|
|
8
|
+
- Support OpenCode long-context pricing tiers via `context_over_200k` when estimating API-equivalent cost.
|
|
9
|
+
- Bump the usage billing cache version so `/qday`, `/qweek`, and `/qmonth` recompute historical API cost with the updated rules.
|
|
10
|
+
- Document API-cost estimation, billing-cache behavior, and child-session aggregation semantics in the README.
|
|
7
11
|
|
|
8
12
|
## 1.13.2
|
|
9
13
|
|
package/README.md
CHANGED
|
@@ -290,8 +290,11 @@ Other defaults:
|
|
|
290
290
|
- `sidebar.childrenConcurrency` controls parallel fetches for descendant session messages (default: `5`, clamped 1–10).
|
|
291
291
|
- `output` includes reasoning tokens (`output = tokens.output + tokens.reasoning`). Reasoning is not rendered as a separate line.
|
|
292
292
|
- API cost bills reasoning tokens at the output rate (same as completion tokens).
|
|
293
|
+
- API cost is computed from OpenCode model pricing metadata, not from `message.cost`. This keeps subscription-backed providers such as OpenAI OAuth usable for API-equivalent cost estimation even when OpenCode's measured cost is `0`.
|
|
294
|
+
- When OpenCode exposes a long-context tier like `context_over_200k`, the plugin uses that premium rate for the whole request once `input > 200000`, matching OpenCode's current pricing schema.
|
|
293
295
|
- `quota.providers` is the extensible per-adapter switch map.
|
|
294
296
|
- If API Cost is `$0.00`, it usually means the model/provider has no pricing mapping in OpenCode at the moment, so equivalent API cost cannot be estimated.
|
|
297
|
+
- Usage chunks cache both measured `cost` and computed `apiCost`. `quota_summary` (`/qday`, `/qweek`, `/qmonth`) usually reads those cached aggregates first, but a billing-cache version bump or missing/legacy API-cost data will trigger a rescan and persist refreshed values.
|
|
295
298
|
|
|
296
299
|
### Buzz provider example
|
|
297
300
|
|
|
@@ -424,6 +427,13 @@ Mixed with Buzz balance:
|
|
|
424
427
|
|
|
425
428
|
`quota_summary` also supports an optional `includeChildren` flag (only effective for `period=session`) to override the config per call. For `day`/`week`/`month` periods, children are never merged — each session is counted independently.
|
|
426
429
|
|
|
430
|
+
## Billing cache behavior
|
|
431
|
+
|
|
432
|
+
- Cached per-session usage stores token totals, measured `cost`, computed `apiCost`, provider breakdowns, and the incremental cursor.
|
|
433
|
+
- Session-scoped sidebar aggregation can merge descendant subagents when `sidebar.includeChildren=true` (default). Measured `cost` stays aligned with the root session's OpenCode `message.cost`, while API-equivalent cost still includes descendant usage.
|
|
434
|
+
- Range tools such as `/qday`, `/qweek`, and `/qmonth` do not merge children. They aggregate each session independently across the selected time window.
|
|
435
|
+
- When API-cost logic changes, the plugin bumps an internal billing-cache version so historical range reports are recomputed with the new rules the next time they are queried.
|
|
436
|
+
|
|
427
437
|
## Debug logging
|
|
428
438
|
|
|
429
439
|
Set `OPENCODE_QUOTA_DEBUG=1` to enable debug logging to stderr. This logs:
|
package/dist/cost.d.ts
CHANGED
|
@@ -6,6 +6,12 @@ export type ModelCostRates = {
|
|
|
6
6
|
output: number;
|
|
7
7
|
cacheRead: number;
|
|
8
8
|
cacheWrite: number;
|
|
9
|
+
contextOver200k?: {
|
|
10
|
+
input: number;
|
|
11
|
+
output: number;
|
|
12
|
+
cacheRead: number;
|
|
13
|
+
cacheWrite: number;
|
|
14
|
+
};
|
|
9
15
|
};
|
|
10
16
|
export declare function modelCostKey(providerID: string, modelID: string): string;
|
|
11
17
|
export declare function parseModelCostRates(value: unknown): ModelCostRates | undefined;
|
package/dist/cost.js
CHANGED
|
@@ -42,14 +42,28 @@ export function parseModelCostRates(value) {
|
|
|
42
42
|
const output = readRate(value.output ?? value.completion);
|
|
43
43
|
const cacheRead = readRate(value.cache_read ?? cache?.read);
|
|
44
44
|
const cacheWrite = readRate(value.cache_write ?? cache?.write);
|
|
45
|
+
const contextOver200k = isRecord(value.context_over_200k)
|
|
46
|
+
? {
|
|
47
|
+
input: readRate(value.context_over_200k.input),
|
|
48
|
+
output: readRate(value.context_over_200k.output),
|
|
49
|
+
cacheRead: readRate(value.context_over_200k.cache_read),
|
|
50
|
+
cacheWrite: readRate(value.context_over_200k.cache_write),
|
|
51
|
+
}
|
|
52
|
+
: undefined;
|
|
45
53
|
if (input <= 0 && output <= 0 && cacheRead <= 0 && cacheWrite <= 0) {
|
|
46
54
|
return undefined;
|
|
47
55
|
}
|
|
56
|
+
const hasContextTier = !!contextOver200k &&
|
|
57
|
+
(contextOver200k.input > 0 ||
|
|
58
|
+
contextOver200k.output > 0 ||
|
|
59
|
+
contextOver200k.cacheRead > 0 ||
|
|
60
|
+
contextOver200k.cacheWrite > 0);
|
|
48
61
|
return {
|
|
49
62
|
input,
|
|
50
63
|
output,
|
|
51
64
|
cacheRead,
|
|
52
65
|
cacheWrite,
|
|
66
|
+
contextOver200k: hasContextTier ? contextOver200k : undefined,
|
|
53
67
|
};
|
|
54
68
|
}
|
|
55
69
|
const MODEL_COST_DIVISOR_PER_TOKEN = 1;
|
|
@@ -65,15 +79,18 @@ export function guessModelCostDivisor(rates) {
|
|
|
65
79
|
: MODEL_COST_DIVISOR_PER_TOKEN;
|
|
66
80
|
}
|
|
67
81
|
export function calcEquivalentApiCostForMessage(message, rates) {
|
|
82
|
+
const effectiveRates = message.tokens.input > 200_000 && rates.contextOver200k
|
|
83
|
+
? rates.contextOver200k
|
|
84
|
+
: rates;
|
|
68
85
|
// For providers that expose reasoning tokens separately, they are still
|
|
69
86
|
// billed as output/completion tokens (same unit price). Our UI also merges
|
|
70
87
|
// reasoning into the single Output statistic, so API cost should match that.
|
|
71
88
|
const billedOutput = message.tokens.output + message.tokens.reasoning;
|
|
72
|
-
const rawCost = message.tokens.input *
|
|
73
|
-
billedOutput *
|
|
74
|
-
message.tokens.cache.read *
|
|
75
|
-
message.tokens.cache.write *
|
|
76
|
-
const divisor = guessModelCostDivisor(
|
|
89
|
+
const rawCost = message.tokens.input * effectiveRates.input +
|
|
90
|
+
billedOutput * effectiveRates.output +
|
|
91
|
+
message.tokens.cache.read * effectiveRates.cacheRead +
|
|
92
|
+
message.tokens.cache.write * effectiveRates.cacheWrite;
|
|
93
|
+
const divisor = guessModelCostDivisor(effectiveRates);
|
|
77
94
|
const normalized = rawCost / divisor;
|
|
78
95
|
return Number.isFinite(normalized) && normalized > 0 ? normalized : 0;
|
|
79
96
|
}
|
|
@@ -140,7 +140,7 @@ async function fetchBuzzQuota({ sourceProviderID, providerID, providerOptions, a
|
|
|
140
140
|
resetAt,
|
|
141
141
|
balance: {
|
|
142
142
|
amount: balance,
|
|
143
|
-
currency: '
|
|
143
|
+
currency: '¥',
|
|
144
144
|
},
|
|
145
145
|
note: 'remaining balance = soft_limit_usd - total_usage / 100',
|
|
146
146
|
};
|
package/dist/usage.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AssistantMessage, Message } from '@opencode-ai/sdk';
|
|
2
2
|
import type { CachedSessionUsage, IncrementalCursor } from './types.js';
|
|
3
|
-
export declare const USAGE_BILLING_CACHE_VERSION =
|
|
3
|
+
export declare const USAGE_BILLING_CACHE_VERSION = 2;
|
|
4
4
|
export type ProviderUsage = {
|
|
5
5
|
providerID: string;
|
|
6
6
|
input: number;
|
|
@@ -47,6 +47,8 @@ export declare function summarizeMessagesIncremental(entries: Array<{
|
|
|
47
47
|
usage: UsageSummary;
|
|
48
48
|
cursor: IncrementalCursor;
|
|
49
49
|
};
|
|
50
|
-
export declare function mergeUsage(target: UsageSummary, source: UsageSummary
|
|
50
|
+
export declare function mergeUsage(target: UsageSummary, source: UsageSummary, options?: {
|
|
51
|
+
includeCost?: boolean;
|
|
52
|
+
}): UsageSummary;
|
|
51
53
|
export declare function toCachedSessionUsage(summary: UsageSummary): CachedSessionUsage;
|
|
52
54
|
export declare function fromCachedSessionUsage(cached: CachedSessionUsage, sessionCount?: number): UsageSummary;
|
package/dist/usage.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const USAGE_BILLING_CACHE_VERSION =
|
|
1
|
+
export const USAGE_BILLING_CACHE_VERSION = 2;
|
|
2
2
|
export function emptyUsageSummary() {
|
|
3
3
|
return {
|
|
4
4
|
input: 0,
|
|
@@ -263,13 +263,16 @@ function findLastCompletedAssistant(entries) {
|
|
|
263
263
|
}
|
|
264
264
|
return best;
|
|
265
265
|
}
|
|
266
|
-
export function mergeUsage(target, source) {
|
|
266
|
+
export function mergeUsage(target, source, options) {
|
|
267
|
+
const includeCost = options?.includeCost !== false;
|
|
267
268
|
target.input += source.input;
|
|
268
269
|
target.output += source.output;
|
|
269
270
|
target.cacheRead += source.cacheRead;
|
|
270
271
|
target.cacheWrite += source.cacheWrite;
|
|
271
272
|
target.total += source.total;
|
|
272
|
-
|
|
273
|
+
if (includeCost) {
|
|
274
|
+
target.cost += source.cost;
|
|
275
|
+
}
|
|
273
276
|
target.apiCost += source.apiCost;
|
|
274
277
|
target.assistantMessages += source.assistantMessages;
|
|
275
278
|
target.sessionCount += source.sessionCount;
|
|
@@ -281,7 +284,9 @@ export function mergeUsage(target, source) {
|
|
|
281
284
|
existing.cacheRead += provider.cacheRead;
|
|
282
285
|
existing.cacheWrite += provider.cacheWrite;
|
|
283
286
|
existing.total += provider.total;
|
|
284
|
-
|
|
287
|
+
if (includeCost) {
|
|
288
|
+
existing.cost += provider.cost;
|
|
289
|
+
}
|
|
285
290
|
existing.apiCost += provider.apiCost;
|
|
286
291
|
existing.assistantMessages += provider.assistantMessages;
|
|
287
292
|
target.providers[provider.providerID] = existing;
|
package/dist/usage_service.js
CHANGED
|
@@ -279,7 +279,11 @@ export function createUsageService(deps) {
|
|
|
279
279
|
for (const childID of descendantIDs) {
|
|
280
280
|
const cached = deps.state.sessions[childID]?.usage;
|
|
281
281
|
if (cached && !isDirty(childID) && isUsageBillingCurrent(cached)) {
|
|
282
|
-
|
|
282
|
+
// Keep measured cost aligned with OpenCode session semantics by only
|
|
283
|
+
// using child sessions for token/API-cost aggregation.
|
|
284
|
+
mergeUsage(merged, fromCachedSessionUsage(cached, 1), {
|
|
285
|
+
includeCost: false,
|
|
286
|
+
});
|
|
283
287
|
}
|
|
284
288
|
else {
|
|
285
289
|
needsFetch.push(childID);
|
|
@@ -294,7 +298,7 @@ export function createUsageService(deps) {
|
|
|
294
298
|
return child.usage;
|
|
295
299
|
});
|
|
296
300
|
for (const childUsage of fetched) {
|
|
297
|
-
mergeUsage(merged, childUsage);
|
|
301
|
+
mergeUsage(merged, childUsage, { includeCost: false });
|
|
298
302
|
}
|
|
299
303
|
}
|
|
300
304
|
return merged;
|