@leo000001/opencode-quota-sidebar 1.13.7 → 1.13.8
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 +4 -0
- package/dist/cost.d.ts +3 -2
- package/dist/cost.js +21 -8
- package/dist/usage_service.js +57 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,6 +63,10 @@ Want to add support for another provider (Google Antigravity, Zhipu AI, Firmware
|
|
|
63
63
|
- RightCode daily quota shows `$remaining/$dailyTotal` + expiry (e.g. `RC Daily $105/$60 Exp 02-27`, without trailing percent) and also shows balance on the next indented line when available; `Exp` remains date-only
|
|
64
64
|
- Session-scoped usage/quota can include descendant subagent sessions (enabled by default via `sidebar.includeChildren=true`). Traversal is bounded by `childrenMaxDepth` (default 6), `childrenMaxSessions` (default 128), and `childrenConcurrency` (default 5); truncation is logged when `OPENCODE_QUOTA_DEBUG=1`. Day/week/month ranges never merge children — only session scope does.
|
|
65
65
|
- Toast message includes three sections: `Token Usage`, `Cost as API` (per provider), and `Quota`
|
|
66
|
+
- OpenAI priority API-cost detection uses two fallbacks in order:
|
|
67
|
+
- message metadata: `openai.serviceTier` / `openai.service_tier`
|
|
68
|
+
- model defaults from `provider.list`: `models[modelID].options.serviceTier`
|
|
69
|
+
- provider aliases with `npm: "@ai-sdk/openai"` are treated as OpenAI for this billing rule
|
|
66
70
|
- Quota snapshots are de-duplicated before rendering to avoid repeated provider lines
|
|
67
71
|
- Custom tools:
|
|
68
72
|
- `quota_summary` — generate usage report for session/day/week/month (markdown + toast)
|
package/dist/cost.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AssistantMessage } from '@opencode-ai/sdk';
|
|
2
2
|
export declare const SUBSCRIPTION_API_COST_PROVIDERS: Set<string>;
|
|
3
|
-
export declare function canonicalApiCostProviderID(providerID: string): string;
|
|
3
|
+
export declare function canonicalApiCostProviderID(providerID: string, npmPackage?: string): string;
|
|
4
4
|
export type ModelCostRates = {
|
|
5
5
|
input: number;
|
|
6
6
|
output: number;
|
|
@@ -13,7 +13,8 @@ export type ModelCostRates = {
|
|
|
13
13
|
cacheWrite: number;
|
|
14
14
|
};
|
|
15
15
|
};
|
|
16
|
+
export declare function openAIServiceTierFromMessage(message: AssistantMessage): string | undefined;
|
|
16
17
|
export declare function modelCostKey(providerID: string, modelID: string): string;
|
|
17
18
|
export declare function parseModelCostRates(value: unknown): ModelCostRates | undefined;
|
|
18
19
|
export declare function guessModelCostDivisor(rates: ModelCostRates): 1 | 1000000;
|
|
19
|
-
export declare function calcEquivalentApiCostForMessage(message: AssistantMessage, rates: ModelCostRates): number;
|
|
20
|
+
export declare function calcEquivalentApiCostForMessage(message: AssistantMessage, rates: ModelCostRates, canonicalProviderID?: string, serviceTier?: string | undefined): number;
|
package/dist/cost.js
CHANGED
|
@@ -5,7 +5,20 @@ function normalizeKnownProviderID(providerID) {
|
|
|
5
5
|
return 'github-copilot';
|
|
6
6
|
return providerID;
|
|
7
7
|
}
|
|
8
|
-
|
|
8
|
+
function canonicalProviderByNpmPackage(npmPackage) {
|
|
9
|
+
const normalized = npmPackage.trim().toLowerCase();
|
|
10
|
+
if (normalized === '@ai-sdk/openai')
|
|
11
|
+
return 'openai';
|
|
12
|
+
if (normalized === '@ai-sdk/anthropic')
|
|
13
|
+
return 'anthropic';
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
export function canonicalApiCostProviderID(providerID, npmPackage) {
|
|
17
|
+
const byPackage = typeof npmPackage === 'string'
|
|
18
|
+
? canonicalProviderByNpmPackage(npmPackage)
|
|
19
|
+
: undefined;
|
|
20
|
+
if (byPackage)
|
|
21
|
+
return byPackage;
|
|
9
22
|
const normalized = normalizeKnownProviderID(providerID);
|
|
10
23
|
if (SUBSCRIPTION_API_COST_PROVIDERS.has(normalized))
|
|
11
24
|
return normalized;
|
|
@@ -19,6 +32,11 @@ export function canonicalApiCostProviderID(providerID) {
|
|
|
19
32
|
}
|
|
20
33
|
return normalized;
|
|
21
34
|
}
|
|
35
|
+
export function openAIServiceTierFromMessage(message) {
|
|
36
|
+
const info = message;
|
|
37
|
+
return (info.providerMetadata?.openai?.serviceTier ??
|
|
38
|
+
info.providerMetadata?.openai?.service_tier);
|
|
39
|
+
}
|
|
22
40
|
export function modelCostKey(providerID, modelID) {
|
|
23
41
|
return `${providerID}:${modelID}`;
|
|
24
42
|
}
|
|
@@ -78,16 +96,11 @@ export function guessModelCostDivisor(rates) {
|
|
|
78
96
|
? MODEL_COST_DIVISOR_PER_MILLION
|
|
79
97
|
: MODEL_COST_DIVISOR_PER_TOKEN;
|
|
80
98
|
}
|
|
81
|
-
export function calcEquivalentApiCostForMessage(message, rates) {
|
|
82
|
-
const info = message;
|
|
99
|
+
export function calcEquivalentApiCostForMessage(message, rates, canonicalProviderID = message.providerID, serviceTier = openAIServiceTierFromMessage(message)) {
|
|
83
100
|
const effectiveRates = message.tokens.input > 200_000 && rates.contextOver200k
|
|
84
101
|
? rates.contextOver200k
|
|
85
102
|
: rates;
|
|
86
|
-
const
|
|
87
|
-
info.providerMetadata?.openai?.service_tier;
|
|
88
|
-
const priorityMultiplier = message.providerID === 'openai' && serviceTier === 'priority'
|
|
89
|
-
? 2
|
|
90
|
-
: 1;
|
|
103
|
+
const priorityMultiplier = canonicalProviderID === 'openai' && serviceTier === 'priority' ? 2 : 1;
|
|
91
104
|
// For providers that expose reasoning tokens separately, they are still
|
|
92
105
|
// billed as output/completion tokens (same unit price). Our UI also merges
|
|
93
106
|
// reasoning into the single Output statistic, so API cost should match that.
|
package/dist/usage_service.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TtlValueCache } from './cache.js';
|
|
2
|
-
import { calcEquivalentApiCostForMessage, canonicalApiCostProviderID, modelCostKey, parseModelCostRates, SUBSCRIPTION_API_COST_PROVIDERS, } from './cost.js';
|
|
2
|
+
import { calcEquivalentApiCostForMessage, canonicalApiCostProviderID, modelCostKey, openAIServiceTierFromMessage, parseModelCostRates, SUBSCRIPTION_API_COST_PROVIDERS, } from './cost.js';
|
|
3
3
|
import { dateKeyFromTimestamp, scanSessionsByCreatedRange, updateSessionsInDayChunks, } from './storage.js';
|
|
4
4
|
import { periodStart } from './period.js';
|
|
5
5
|
import { debug, isRecord, mapConcurrent, swallow } from './helpers.js';
|
|
@@ -30,7 +30,7 @@ export function createUsageService(deps) {
|
|
|
30
30
|
return cached;
|
|
31
31
|
const providerClient = deps.client;
|
|
32
32
|
if (!providerClient.provider?.list) {
|
|
33
|
-
return modelCostCache.set({}, 30_000);
|
|
33
|
+
return modelCostCache.set({ rates: {}, providerAliases: {}, modelServiceTiers: {} }, 30_000);
|
|
34
34
|
}
|
|
35
35
|
const response = await providerClient.provider
|
|
36
36
|
.list({
|
|
@@ -45,11 +45,56 @@ export function createUsageService(deps) {
|
|
|
45
45
|
Array.isArray(response.data.all)
|
|
46
46
|
? response.data.all
|
|
47
47
|
: [];
|
|
48
|
-
const
|
|
48
|
+
const providerAliases = all.reduce((acc, provider) => {
|
|
49
|
+
if (!isRecord(provider))
|
|
50
|
+
return acc;
|
|
51
|
+
if (typeof provider.id !== 'string')
|
|
52
|
+
return acc;
|
|
53
|
+
const canonical = canonicalApiCostProviderID(provider.id, typeof provider.npm === 'string' ? provider.npm : undefined);
|
|
54
|
+
if (!SUBSCRIPTION_API_COST_PROVIDERS.has(canonical))
|
|
55
|
+
return acc;
|
|
56
|
+
acc[provider.id] = canonical;
|
|
57
|
+
return acc;
|
|
58
|
+
}, {});
|
|
59
|
+
const modelServiceTiers = all.reduce((acc, provider) => {
|
|
60
|
+
if (!isRecord(provider))
|
|
61
|
+
return acc;
|
|
62
|
+
const providerID = typeof provider.id === 'string'
|
|
63
|
+
? (providerAliases[provider.id] ??
|
|
64
|
+
canonicalApiCostProviderID(provider.id, typeof provider.npm === 'string' ? provider.npm : undefined))
|
|
65
|
+
: undefined;
|
|
66
|
+
if (!providerID)
|
|
67
|
+
return acc;
|
|
68
|
+
if (!SUBSCRIPTION_API_COST_PROVIDERS.has(providerID))
|
|
69
|
+
return acc;
|
|
70
|
+
const models = provider.models;
|
|
71
|
+
if (!isRecord(models))
|
|
72
|
+
return acc;
|
|
73
|
+
for (const [modelKey, modelValue] of Object.entries(models)) {
|
|
74
|
+
if (!isRecord(modelValue))
|
|
75
|
+
continue;
|
|
76
|
+
const options = isRecord(modelValue.options)
|
|
77
|
+
? modelValue.options
|
|
78
|
+
: undefined;
|
|
79
|
+
const serviceTier = typeof options?.serviceTier === 'string'
|
|
80
|
+
? options.serviceTier
|
|
81
|
+
: undefined;
|
|
82
|
+
if (!serviceTier)
|
|
83
|
+
continue;
|
|
84
|
+
const modelID = typeof modelValue.id === 'string' ? modelValue.id : modelKey;
|
|
85
|
+
acc[modelCostKey(providerID, modelID)] = serviceTier;
|
|
86
|
+
if (modelKey !== modelID) {
|
|
87
|
+
acc[modelCostKey(providerID, modelKey)] = serviceTier;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return acc;
|
|
91
|
+
}, {});
|
|
92
|
+
const rates = all.reduce((acc, provider) => {
|
|
49
93
|
if (!isRecord(provider))
|
|
50
94
|
return acc;
|
|
51
95
|
const providerID = typeof provider.id === 'string'
|
|
52
|
-
?
|
|
96
|
+
? (providerAliases[provider.id] ??
|
|
97
|
+
canonicalApiCostProviderID(provider.id, typeof provider.npm === 'string' ? provider.npm : undefined))
|
|
53
98
|
: undefined;
|
|
54
99
|
if (!providerID)
|
|
55
100
|
return acc;
|
|
@@ -72,13 +117,15 @@ export function createUsageService(deps) {
|
|
|
72
117
|
}
|
|
73
118
|
return acc;
|
|
74
119
|
}, {});
|
|
75
|
-
return modelCostCache.set(
|
|
120
|
+
return modelCostCache.set({ rates, providerAliases, modelServiceTiers }, Math.max(30_000, deps.config.quota.refreshMs));
|
|
76
121
|
};
|
|
77
122
|
const calcEquivalentApiCost = (message, modelCostMap) => {
|
|
78
|
-
const providerID =
|
|
123
|
+
const providerID = typeof modelCostMap.providerAliases[message.providerID] === 'string'
|
|
124
|
+
? modelCostMap.providerAliases[message.providerID]
|
|
125
|
+
: canonicalApiCostProviderID(message.providerID);
|
|
79
126
|
if (!SUBSCRIPTION_API_COST_PROVIDERS.has(providerID))
|
|
80
127
|
return 0;
|
|
81
|
-
const rates = modelCostMap[modelCostKey(providerID, message.modelID)];
|
|
128
|
+
const rates = modelCostMap.rates[modelCostKey(providerID, message.modelID)];
|
|
82
129
|
if (!rates) {
|
|
83
130
|
const key = modelCostKey(providerID, message.modelID);
|
|
84
131
|
if (!missingApiCostRateKeys.has(key)) {
|
|
@@ -87,7 +134,9 @@ export function createUsageService(deps) {
|
|
|
87
134
|
}
|
|
88
135
|
return 0;
|
|
89
136
|
}
|
|
90
|
-
|
|
137
|
+
const serviceTier = openAIServiceTierFromMessage(message) ??
|
|
138
|
+
modelCostMap.modelServiceTiers[modelCostKey(providerID, message.modelID)];
|
|
139
|
+
return calcEquivalentApiCostForMessage(message, rates, providerID, serviceTier);
|
|
91
140
|
};
|
|
92
141
|
const isFiniteNumber = (value) => typeof value === 'number' && Number.isFinite(value);
|
|
93
142
|
const decodeTokens = (value) => {
|