@leo000001/opencode-quota-sidebar 2.0.15 → 2.0.20
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/dist/cost.d.ts +29 -0
- package/dist/cost.js +276 -36
- package/dist/format.js +22 -6
- package/dist/providers/core/zhipu_coding_plan.d.ts +2 -0
- package/dist/providers/core/zhipu_coding_plan.js +195 -0
- package/dist/providers/index.d.ts +2 -1
- package/dist/providers/index.js +3 -1
- package/dist/quota.js +7 -1
- package/dist/quota_render.js +3 -0
- package/dist/storage_parse.js +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/usage.d.ts +1 -1
- package/dist/usage.js +1 -1
- package/dist/usage_service.js +49 -30
- package/package.json +1 -1
package/dist/cost.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { AssistantMessage } from '@opencode-ai/sdk';
|
|
2
2
|
import type { CacheCoverageMode } from './types.js';
|
|
3
3
|
export declare const API_COST_ENABLED_PROVIDERS: Set<string>;
|
|
4
|
+
export type CanonicalPriceSource = 'official-doc' | 'runtime';
|
|
5
|
+
export declare function canonicalPricingProviderID(providerID: string): string;
|
|
4
6
|
export declare function canonicalApiCostProviderID(providerID: string): string;
|
|
5
7
|
export type ModelCostRates = {
|
|
6
8
|
input: number;
|
|
@@ -14,11 +16,38 @@ export type ModelCostRates = {
|
|
|
14
16
|
cacheWrite: number;
|
|
15
17
|
};
|
|
16
18
|
};
|
|
19
|
+
export type CanonicalPriceEntry = {
|
|
20
|
+
provider: string;
|
|
21
|
+
model: string;
|
|
22
|
+
rates: ModelCostRates;
|
|
23
|
+
source: CanonicalPriceSource;
|
|
24
|
+
sourceURL?: string;
|
|
25
|
+
updatedAt?: string;
|
|
26
|
+
};
|
|
17
27
|
export declare function modelCostKey(providerID: string, modelID: string): string;
|
|
18
28
|
export declare function modelCostLookupKeys(providerID: string, modelID: string): string[];
|
|
19
29
|
export declare function getBundledModelCostMap(): {
|
|
20
30
|
[x: string]: ModelCostRates;
|
|
21
31
|
};
|
|
32
|
+
export declare function getBundledCanonicalPriceEntries(): {
|
|
33
|
+
rates: {
|
|
34
|
+
contextOver200k: {
|
|
35
|
+
input: number;
|
|
36
|
+
output: number;
|
|
37
|
+
cacheRead: number;
|
|
38
|
+
cacheWrite: number;
|
|
39
|
+
} | undefined;
|
|
40
|
+
input: number;
|
|
41
|
+
output: number;
|
|
42
|
+
cacheRead: number;
|
|
43
|
+
cacheWrite: number;
|
|
44
|
+
};
|
|
45
|
+
provider: string;
|
|
46
|
+
model: string;
|
|
47
|
+
source: CanonicalPriceSource;
|
|
48
|
+
sourceURL?: string;
|
|
49
|
+
updatedAt?: string;
|
|
50
|
+
}[];
|
|
22
51
|
export declare function parseModelCostRates(value: unknown): ModelCostRates | undefined;
|
|
23
52
|
export declare function guessModelCostDivisor(rates: ModelCostRates): 1 | 1000000;
|
|
24
53
|
export declare function cacheCoverageModeFromRates(rates: ModelCostRates | undefined): CacheCoverageMode;
|
package/dist/cost.js
CHANGED
|
@@ -3,11 +3,73 @@ export const API_COST_ENABLED_PROVIDERS = new Set([
|
|
|
3
3
|
'openai',
|
|
4
4
|
'anthropic',
|
|
5
5
|
'kimi-for-coding',
|
|
6
|
+
'zhipu',
|
|
6
7
|
]);
|
|
7
8
|
const MODEL_COST_RATE_ALIASES = {
|
|
8
|
-
'
|
|
9
|
-
'
|
|
9
|
+
'zhipuai-coding-plan:glm-5.1': ['zhipu:glm-5'],
|
|
10
|
+
'zhipuai-coding-plan:glm-5.1-thinking': ['zhipu:glm-5'],
|
|
11
|
+
'zhipu:glm-5.1': ['zhipu:glm-5'],
|
|
12
|
+
'zhipu:glm-5.1-thinking': ['zhipu:glm-5'],
|
|
10
13
|
};
|
|
14
|
+
function moonshotCanonicalModelID(modelID) {
|
|
15
|
+
const stripped = modelID.replace(/^moonshotai[/:]/i, '');
|
|
16
|
+
switch (stripped) {
|
|
17
|
+
case 'k2p5':
|
|
18
|
+
case 'kimi-k2-5':
|
|
19
|
+
return 'kimi-k2.5';
|
|
20
|
+
default:
|
|
21
|
+
return stripped;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function moonshotModelAliases(modelID, options) {
|
|
25
|
+
const aliases = [];
|
|
26
|
+
const push = (value) => {
|
|
27
|
+
if (!value)
|
|
28
|
+
return;
|
|
29
|
+
if (!aliases.includes(value))
|
|
30
|
+
aliases.push(value);
|
|
31
|
+
};
|
|
32
|
+
const stripped = modelID.replace(/^moonshotai[/:]/i, '');
|
|
33
|
+
const canonical = moonshotCanonicalModelID(modelID);
|
|
34
|
+
if (!options?.canonicalProviderKeys)
|
|
35
|
+
push(modelID);
|
|
36
|
+
if (stripped !== modelID)
|
|
37
|
+
push(stripped);
|
|
38
|
+
push(canonical);
|
|
39
|
+
return aliases;
|
|
40
|
+
}
|
|
41
|
+
function zhipuModelAliases(modelID) {
|
|
42
|
+
const aliases = [];
|
|
43
|
+
const queue = [];
|
|
44
|
+
const push = (value) => {
|
|
45
|
+
if (!value)
|
|
46
|
+
return;
|
|
47
|
+
if (!aliases.includes(value)) {
|
|
48
|
+
aliases.push(value);
|
|
49
|
+
queue.push(value);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
push(modelID);
|
|
53
|
+
for (let index = 0; index < queue.length; index++) {
|
|
54
|
+
const stem = queue[index];
|
|
55
|
+
const withoutProviderPrefix = stem.replace(/^(?:zhipu|z-ai|bigmodel|zhipuai-coding-plan)[/:]/, '');
|
|
56
|
+
push(withoutProviderPrefix);
|
|
57
|
+
push(`zhipu/${withoutProviderPrefix}`);
|
|
58
|
+
const withoutBillingSuffix = withoutProviderPrefix.replace(/-billing$/, '');
|
|
59
|
+
push(withoutBillingSuffix);
|
|
60
|
+
push(`zhipu/${withoutBillingSuffix}`);
|
|
61
|
+
const withoutThinkingSuffix = withoutBillingSuffix.replace(/-thinking$/, '');
|
|
62
|
+
push(withoutThinkingSuffix);
|
|
63
|
+
push(`zhipu/${withoutThinkingSuffix}`);
|
|
64
|
+
const dotted = withoutThinkingSuffix.replace(/(\d)-(\d)(?=-|$)/g, '$1.$2');
|
|
65
|
+
push(dotted);
|
|
66
|
+
push(`zhipu/${dotted}`);
|
|
67
|
+
const hyphenated = withoutThinkingSuffix.replace(/(\d)\.(\d)(?=-|$)/g, '$1-$2');
|
|
68
|
+
push(hyphenated);
|
|
69
|
+
push(`zhipu/${hyphenated}`);
|
|
70
|
+
}
|
|
71
|
+
return aliases;
|
|
72
|
+
}
|
|
11
73
|
function anthropicModelAliases(modelID) {
|
|
12
74
|
const aliases = [];
|
|
13
75
|
const queue = [];
|
|
@@ -61,6 +123,29 @@ function normalizeKnownProviderID(providerID) {
|
|
|
61
123
|
return 'github-copilot';
|
|
62
124
|
return providerID;
|
|
63
125
|
}
|
|
126
|
+
function isCanonicalZhipuProviderID(providerID) {
|
|
127
|
+
return (providerID === 'zhipu' ||
|
|
128
|
+
providerID === 'bigmodel' ||
|
|
129
|
+
providerID === 'z-ai' ||
|
|
130
|
+
providerID === 'zhipuai-coding-plan');
|
|
131
|
+
}
|
|
132
|
+
export function canonicalPricingProviderID(providerID) {
|
|
133
|
+
const normalized = normalizeKnownProviderID(providerID);
|
|
134
|
+
const lowered = normalized.toLowerCase();
|
|
135
|
+
if (isCanonicalZhipuProviderID(lowered)) {
|
|
136
|
+
return 'zhipu';
|
|
137
|
+
}
|
|
138
|
+
if (lowered === 'kimi-for-coding')
|
|
139
|
+
return 'moonshotai';
|
|
140
|
+
if (lowered.includes('anthropic') || lowered.includes('claude')) {
|
|
141
|
+
return 'anthropic';
|
|
142
|
+
}
|
|
143
|
+
if (lowered.includes('openai') || lowered.endsWith('-oai'))
|
|
144
|
+
return 'openai';
|
|
145
|
+
if (lowered.includes('copilot'))
|
|
146
|
+
return 'github-copilot';
|
|
147
|
+
return normalized;
|
|
148
|
+
}
|
|
64
149
|
export function canonicalApiCostProviderID(providerID) {
|
|
65
150
|
const normalized = normalizeKnownProviderID(providerID);
|
|
66
151
|
if (API_COST_ENABLED_PROVIDERS.has(normalized))
|
|
@@ -73,6 +158,9 @@ export function canonicalApiCostProviderID(providerID) {
|
|
|
73
158
|
if (lowered.includes('anthropic') || lowered.includes('claude')) {
|
|
74
159
|
return 'anthropic';
|
|
75
160
|
}
|
|
161
|
+
if (isCanonicalZhipuProviderID(lowered)) {
|
|
162
|
+
return 'zhipu';
|
|
163
|
+
}
|
|
76
164
|
return normalized;
|
|
77
165
|
}
|
|
78
166
|
function anthropicPricing(input, output, options) {
|
|
@@ -96,77 +184,210 @@ function anthropicPricing(input, output, options) {
|
|
|
96
184
|
: undefined,
|
|
97
185
|
};
|
|
98
186
|
}
|
|
99
|
-
|
|
187
|
+
function zhipuPricing(input, output, cacheRead) {
|
|
188
|
+
return {
|
|
189
|
+
input,
|
|
190
|
+
output,
|
|
191
|
+
cacheRead,
|
|
192
|
+
cacheWrite: 0,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function moonshotPricing(input, output, cacheRead) {
|
|
196
|
+
return {
|
|
197
|
+
input,
|
|
198
|
+
output,
|
|
199
|
+
cacheRead,
|
|
200
|
+
cacheWrite: 0,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const BUNDLED_CANONICAL_PRICE_ENTRIES = [
|
|
100
204
|
{
|
|
101
|
-
|
|
102
|
-
|
|
205
|
+
provider: 'anthropic',
|
|
206
|
+
model: 'claude-opus-4-6',
|
|
103
207
|
rates: anthropicPricing(5, 25),
|
|
208
|
+
source: 'official-doc',
|
|
209
|
+
sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
|
|
104
210
|
},
|
|
105
211
|
{
|
|
106
|
-
|
|
107
|
-
|
|
212
|
+
provider: 'anthropic',
|
|
213
|
+
model: 'claude-opus-4-5',
|
|
108
214
|
rates: anthropicPricing(5, 25),
|
|
215
|
+
source: 'official-doc',
|
|
216
|
+
sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
|
|
109
217
|
},
|
|
110
218
|
{
|
|
111
|
-
|
|
112
|
-
|
|
219
|
+
provider: 'anthropic',
|
|
220
|
+
model: 'claude-opus-4-1',
|
|
113
221
|
rates: anthropicPricing(15, 75),
|
|
222
|
+
source: 'official-doc',
|
|
223
|
+
sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
|
|
114
224
|
},
|
|
115
225
|
{
|
|
116
|
-
|
|
117
|
-
|
|
226
|
+
provider: 'anthropic',
|
|
227
|
+
model: 'claude-opus-4',
|
|
118
228
|
rates: anthropicPricing(15, 75),
|
|
229
|
+
source: 'official-doc',
|
|
230
|
+
sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
|
|
119
231
|
},
|
|
120
232
|
{
|
|
121
|
-
|
|
122
|
-
|
|
233
|
+
provider: 'anthropic',
|
|
234
|
+
model: 'claude-sonnet-4-6',
|
|
123
235
|
rates: anthropicPricing(3, 15),
|
|
236
|
+
source: 'official-doc',
|
|
237
|
+
sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
|
|
124
238
|
},
|
|
125
239
|
{
|
|
126
|
-
|
|
127
|
-
|
|
240
|
+
provider: 'anthropic',
|
|
241
|
+
model: 'claude-sonnet-4-5',
|
|
128
242
|
rates: anthropicPricing(3, 15, {
|
|
129
243
|
longContextInput: 6,
|
|
130
244
|
longContextOutput: 22.5,
|
|
131
245
|
}),
|
|
246
|
+
source: 'official-doc',
|
|
247
|
+
sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
|
|
132
248
|
},
|
|
133
249
|
{
|
|
134
|
-
|
|
135
|
-
|
|
250
|
+
provider: 'anthropic',
|
|
251
|
+
model: 'claude-sonnet-4',
|
|
136
252
|
rates: anthropicPricing(3, 15, {
|
|
137
253
|
longContextInput: 6,
|
|
138
254
|
longContextOutput: 22.5,
|
|
139
255
|
}),
|
|
256
|
+
source: 'official-doc',
|
|
257
|
+
sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
|
|
140
258
|
},
|
|
141
259
|
{
|
|
142
|
-
|
|
143
|
-
|
|
260
|
+
provider: 'anthropic',
|
|
261
|
+
model: 'claude-3-7-sonnet',
|
|
144
262
|
rates: anthropicPricing(3, 15),
|
|
263
|
+
source: 'official-doc',
|
|
264
|
+
sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
|
|
145
265
|
},
|
|
146
266
|
{
|
|
147
|
-
|
|
148
|
-
|
|
267
|
+
provider: 'anthropic',
|
|
268
|
+
model: 'claude-3-5-sonnet',
|
|
149
269
|
rates: anthropicPricing(3, 15),
|
|
270
|
+
source: 'official-doc',
|
|
271
|
+
sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
|
|
150
272
|
},
|
|
151
273
|
{
|
|
152
|
-
|
|
153
|
-
|
|
274
|
+
provider: 'anthropic',
|
|
275
|
+
model: 'claude-haiku-4-5',
|
|
154
276
|
rates: anthropicPricing(1, 5),
|
|
277
|
+
source: 'official-doc',
|
|
278
|
+
sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
|
|
155
279
|
},
|
|
156
280
|
{
|
|
157
|
-
|
|
158
|
-
|
|
281
|
+
provider: 'anthropic',
|
|
282
|
+
model: 'claude-3-5-haiku',
|
|
159
283
|
rates: anthropicPricing(0.8, 4),
|
|
284
|
+
source: 'official-doc',
|
|
285
|
+
sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
|
|
160
286
|
},
|
|
161
287
|
{
|
|
162
|
-
|
|
163
|
-
|
|
288
|
+
provider: 'anthropic',
|
|
289
|
+
model: 'claude-3-opus',
|
|
164
290
|
rates: anthropicPricing(15, 75),
|
|
291
|
+
source: 'official-doc',
|
|
292
|
+
sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
|
|
165
293
|
},
|
|
166
294
|
{
|
|
167
|
-
|
|
168
|
-
|
|
295
|
+
provider: 'anthropic',
|
|
296
|
+
model: 'claude-3-haiku',
|
|
169
297
|
rates: anthropicPricing(0.25, 1.25),
|
|
298
|
+
source: 'official-doc',
|
|
299
|
+
sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
provider: 'zhipu',
|
|
303
|
+
model: 'glm-5',
|
|
304
|
+
rates: zhipuPricing(1, 3.2, 0.2),
|
|
305
|
+
source: 'official-doc',
|
|
306
|
+
sourceURL: 'https://docs.z.ai/guides/overview/pricing',
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
provider: 'zhipu',
|
|
310
|
+
model: 'glm-4.7',
|
|
311
|
+
rates: zhipuPricing(0.6, 2.2, 0.11),
|
|
312
|
+
source: 'official-doc',
|
|
313
|
+
sourceURL: 'https://docs.z.ai/guides/overview/pricing',
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
provider: 'zhipu',
|
|
317
|
+
model: 'glm-4.6',
|
|
318
|
+
rates: zhipuPricing(0.6, 2.2, 0.11),
|
|
319
|
+
source: 'official-doc',
|
|
320
|
+
sourceURL: 'https://docs.z.ai/guides/overview/pricing',
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
provider: 'zhipu',
|
|
324
|
+
model: 'glm-4.6v',
|
|
325
|
+
rates: zhipuPricing(0.3, 0.9, 0.05),
|
|
326
|
+
source: 'official-doc',
|
|
327
|
+
sourceURL: 'https://docs.z.ai/guides/overview/pricing',
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
provider: 'zhipu',
|
|
331
|
+
model: 'glm-4.5',
|
|
332
|
+
rates: zhipuPricing(0.6, 2.2, 0.11),
|
|
333
|
+
source: 'official-doc',
|
|
334
|
+
sourceURL: 'https://docs.z.ai/guides/overview/pricing',
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
provider: 'zhipu',
|
|
338
|
+
model: 'glm-4.5-air',
|
|
339
|
+
rates: zhipuPricing(0.2, 1.1, 0.03),
|
|
340
|
+
source: 'official-doc',
|
|
341
|
+
sourceURL: 'https://docs.z.ai/guides/overview/pricing',
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
provider: 'zhipu',
|
|
345
|
+
model: 'glm-4.5v',
|
|
346
|
+
rates: zhipuPricing(0.6, 1.8, 0.11),
|
|
347
|
+
source: 'official-doc',
|
|
348
|
+
sourceURL: 'https://docs.z.ai/guides/overview/pricing',
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
provider: 'moonshotai',
|
|
352
|
+
model: 'kimi-k2.5',
|
|
353
|
+
rates: moonshotPricing(0.6, 3, 0.1),
|
|
354
|
+
source: 'official-doc',
|
|
355
|
+
sourceURL: 'https://platform.moonshot.ai/docs/pricing/chat',
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
provider: 'moonshotai',
|
|
359
|
+
model: 'kimi-k2-thinking',
|
|
360
|
+
rates: moonshotPricing(0.6, 2.5, 0.15),
|
|
361
|
+
source: 'official-doc',
|
|
362
|
+
sourceURL: 'https://platform.moonshot.ai/docs/pricing/chat',
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
provider: 'moonshotai',
|
|
366
|
+
model: 'kimi-k2-0711-preview',
|
|
367
|
+
rates: moonshotPricing(0.6, 2.5, 0.15),
|
|
368
|
+
source: 'official-doc',
|
|
369
|
+
sourceURL: 'https://platform.moonshot.ai/docs/pricing/chat',
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
provider: 'moonshotai',
|
|
373
|
+
model: 'kimi-k2-0905-preview',
|
|
374
|
+
rates: moonshotPricing(0.6, 2.5, 0.15),
|
|
375
|
+
source: 'official-doc',
|
|
376
|
+
sourceURL: 'https://platform.moonshot.ai/docs/pricing/chat',
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
provider: 'moonshotai',
|
|
380
|
+
model: 'kimi-k2-turbo-preview',
|
|
381
|
+
rates: moonshotPricing(2.4, 10, 0.6),
|
|
382
|
+
source: 'official-doc',
|
|
383
|
+
sourceURL: 'https://platform.moonshot.ai/docs/pricing/chat',
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
provider: 'moonshotai',
|
|
387
|
+
model: 'kimi-k2-thinking-turbo',
|
|
388
|
+
rates: moonshotPricing(1.15, 8, 0.15),
|
|
389
|
+
source: 'official-doc',
|
|
390
|
+
sourceURL: 'https://platform.moonshot.ai/docs/pricing/chat',
|
|
170
391
|
},
|
|
171
392
|
];
|
|
172
393
|
export function modelCostKey(providerID, modelID) {
|
|
@@ -174,17 +395,25 @@ export function modelCostKey(providerID, modelID) {
|
|
|
174
395
|
}
|
|
175
396
|
export function modelCostLookupKeys(providerID, modelID) {
|
|
176
397
|
const keys = [];
|
|
177
|
-
const canonicalProviderID =
|
|
398
|
+
const canonicalProviderID = canonicalPricingProviderID(providerID);
|
|
178
399
|
const push = (key) => {
|
|
179
400
|
if (!keys.includes(key))
|
|
180
401
|
keys.push(key);
|
|
181
402
|
};
|
|
182
|
-
const
|
|
403
|
+
const modelIDsFor = (options) => canonicalProviderID === 'anthropic'
|
|
183
404
|
? anthropicModelAliases(modelID)
|
|
184
|
-
:
|
|
185
|
-
|
|
405
|
+
: canonicalProviderID === 'zhipu'
|
|
406
|
+
? zhipuModelAliases(modelID)
|
|
407
|
+
: canonicalProviderID === 'moonshotai'
|
|
408
|
+
? moonshotModelAliases(modelID, options)
|
|
409
|
+
: [modelID];
|
|
410
|
+
for (const candidateModelID of modelIDsFor()) {
|
|
186
411
|
push(modelCostKey(providerID, candidateModelID));
|
|
187
|
-
|
|
412
|
+
}
|
|
413
|
+
if (canonicalProviderID !== providerID) {
|
|
414
|
+
for (const candidateModelID of modelIDsFor({
|
|
415
|
+
canonicalProviderKeys: true,
|
|
416
|
+
})) {
|
|
188
417
|
push(modelCostKey(canonicalProviderID, candidateModelID));
|
|
189
418
|
}
|
|
190
419
|
}
|
|
@@ -197,8 +426,8 @@ export function modelCostLookupKeys(providerID, modelID) {
|
|
|
197
426
|
}
|
|
198
427
|
function createBundledModelCostMap() {
|
|
199
428
|
const map = {};
|
|
200
|
-
for (const entry of
|
|
201
|
-
for (const key of modelCostLookupKeys(entry.
|
|
429
|
+
for (const entry of BUNDLED_CANONICAL_PRICE_ENTRIES) {
|
|
430
|
+
for (const key of modelCostLookupKeys(entry.provider, entry.model)) {
|
|
202
431
|
map[key] = entry.rates;
|
|
203
432
|
}
|
|
204
433
|
}
|
|
@@ -208,6 +437,17 @@ const BUNDLED_MODEL_COST_MAP = createBundledModelCostMap();
|
|
|
208
437
|
export function getBundledModelCostMap() {
|
|
209
438
|
return { ...BUNDLED_MODEL_COST_MAP };
|
|
210
439
|
}
|
|
440
|
+
export function getBundledCanonicalPriceEntries() {
|
|
441
|
+
return BUNDLED_CANONICAL_PRICE_ENTRIES.map((entry) => ({
|
|
442
|
+
...entry,
|
|
443
|
+
rates: {
|
|
444
|
+
...entry.rates,
|
|
445
|
+
contextOver200k: entry.rates.contextOver200k
|
|
446
|
+
? { ...entry.rates.contextOver200k }
|
|
447
|
+
: undefined,
|
|
448
|
+
},
|
|
449
|
+
}));
|
|
450
|
+
}
|
|
211
451
|
export function parseModelCostRates(value) {
|
|
212
452
|
if (!isRecord(value))
|
|
213
453
|
return undefined;
|
package/dist/format.js
CHANGED
|
@@ -204,6 +204,8 @@ function compactProviderLabel(quota) {
|
|
|
204
204
|
return 'Ant';
|
|
205
205
|
if (canonical === 'kimi-for-coding')
|
|
206
206
|
return 'Kimi';
|
|
207
|
+
if (canonical === 'zhipuai-coding-plan')
|
|
208
|
+
return 'Zhipu';
|
|
207
209
|
if (canonical === 'rightcode')
|
|
208
210
|
return 'RC';
|
|
209
211
|
if (canonical === 'xyai-vibe')
|
|
@@ -257,26 +259,28 @@ function compactQuotaWindowText(win) {
|
|
|
257
259
|
const resetToken = reset
|
|
258
260
|
? `${compactQuotaResetToken(win.resetLabel)}${reset}`
|
|
259
261
|
: undefined;
|
|
262
|
+
const note = sanitizeLine(win.note || '');
|
|
260
263
|
if (win.showPercent === false) {
|
|
261
264
|
const safe = sanitizeLine(win.label || '');
|
|
262
265
|
const daily = safe ? safe.replace(/^Daily\s+/i, 'D') : '';
|
|
263
|
-
return [daily, resetToken].filter(Boolean).join(' ');
|
|
266
|
+
return [daily, resetToken, note].filter(Boolean).join(' ');
|
|
264
267
|
}
|
|
265
268
|
const percentToken = compactQuotaPercentToken(win.label, win.remainingPercent);
|
|
266
|
-
return [percentToken, resetToken].filter(Boolean).join(' ');
|
|
269
|
+
return [percentToken, resetToken, note].filter(Boolean).join(' ');
|
|
267
270
|
}
|
|
268
271
|
function compactQuotaWindowTokens(win) {
|
|
269
272
|
const reset = compactReset(win.resetAt, win.resetLabel, win.label);
|
|
270
273
|
const resetToken = reset
|
|
271
274
|
? `${compactQuotaResetToken(win.resetLabel)}${reset}`
|
|
272
275
|
: undefined;
|
|
276
|
+
const note = sanitizeLine(win.note || '');
|
|
273
277
|
if (win.showPercent === false) {
|
|
274
278
|
const safe = sanitizeLine(win.label || '');
|
|
275
279
|
const daily = safe ? safe.replace(/^Daily\s+/i, 'D') : '';
|
|
276
|
-
return [daily, resetToken].filter((value) => Boolean(value));
|
|
280
|
+
return [daily, resetToken, note].filter((value) => Boolean(value));
|
|
277
281
|
}
|
|
278
282
|
const percentToken = compactQuotaPercentToken(win.label, win.remainingPercent);
|
|
279
|
-
return [percentToken, resetToken].filter((value) => Boolean(value));
|
|
283
|
+
return [percentToken, resetToken, note].filter((value) => Boolean(value));
|
|
280
284
|
}
|
|
281
285
|
function compactQuotaBalanceText(balance) {
|
|
282
286
|
return `B${compactDesktopCurrencyValue(balance.amount, balance.currency)}`;
|
|
@@ -611,6 +615,8 @@ function compactQuotaWide(quota, labelWidth = 0, options) {
|
|
|
611
615
|
if (reset) {
|
|
612
616
|
parts.push(`${sanitizeLine(win.resetLabel || 'Rst')} ${reset}`);
|
|
613
617
|
}
|
|
618
|
+
if (win.note)
|
|
619
|
+
parts.push(sanitizeLine(win.note));
|
|
614
620
|
return parts.join(' ');
|
|
615
621
|
};
|
|
616
622
|
// Multi-window rendering
|
|
@@ -849,7 +855,9 @@ export function renderMarkdownReport(period, usage, quotas, options) {
|
|
|
849
855
|
// Multi-window detail
|
|
850
856
|
if (quota.windows && quota.windows.length > 0 && quota.status === 'ok') {
|
|
851
857
|
const windowLines = quota.windows.map((win) => {
|
|
852
|
-
const extraNote = win === quota.windows?.[0] && quota.note
|
|
858
|
+
const extraNote = win.note || (win === quota.windows?.[0] && quota.note)
|
|
859
|
+
? ` | ${win.note || quota.note}`
|
|
860
|
+
: '';
|
|
853
861
|
if (win.showPercent === false) {
|
|
854
862
|
const winLabel = win.label ? ` (${win.label})` : '';
|
|
855
863
|
return mdCell(`- ${displayLabel}${winLabel}: ${quota.status} | reset ${reportResetLine(win.resetAt, win.resetLabel, win.label)}${extraNote}`);
|
|
@@ -964,7 +972,13 @@ export function renderToastMessage(period, usage, quotas, options) {
|
|
|
964
972
|
}
|
|
965
973
|
else {
|
|
966
974
|
const hasAnyUsage = Object.keys(usage.providers).length > 0;
|
|
967
|
-
|
|
975
|
+
const hasOnlyCopilotUsage = hasAnyUsage &&
|
|
976
|
+
Object.values(usage.providers).every((provider) => canonicalProviderID(provider.providerID) === 'github-copilot');
|
|
977
|
+
lines.push(fitLine(hasOnlyCopilotUsage
|
|
978
|
+
? ' N/A (Copilot)'
|
|
979
|
+
: hasAnyUsage
|
|
980
|
+
? ' N/A'
|
|
981
|
+
: ' -', width));
|
|
968
982
|
}
|
|
969
983
|
}
|
|
970
984
|
const providerCachePairs = Object.values(usage.providers)
|
|
@@ -995,6 +1009,8 @@ export function renderToastMessage(period, usage, quotas, options) {
|
|
|
995
1009
|
parts.push(pct);
|
|
996
1010
|
if (reset)
|
|
997
1011
|
parts.push(`${win.resetLabel || 'Rst'} ${reset}`);
|
|
1012
|
+
if (win.note)
|
|
1013
|
+
parts.push(win.note);
|
|
998
1014
|
return {
|
|
999
1015
|
label: idx === 0 ? quotaDisplayLabel(item) : '',
|
|
1000
1016
|
value: parts.filter(Boolean).join(' '),
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { isRecord, swallow } from '../../helpers.js';
|
|
2
|
+
import { asNumber, configuredProviderEnabled, fetchWithTimeout, sanitizeBaseURL, toIso, } from '../common.js';
|
|
3
|
+
const ZHIPU_QUOTA_URL = 'https://bigmodel.cn/api/monitor/usage/quota/limit';
|
|
4
|
+
const ZHIPU_INTL_QUOTA_URL = 'https://api.z.ai/api/monitor/usage/quota/limit';
|
|
5
|
+
function resolveApiKey(auth, providerOptions) {
|
|
6
|
+
const optionKey = providerOptions?.apiKey;
|
|
7
|
+
if (typeof optionKey === 'string' && optionKey)
|
|
8
|
+
return optionKey;
|
|
9
|
+
if (!auth)
|
|
10
|
+
return undefined;
|
|
11
|
+
if (auth.type === 'api' && typeof auth.key === 'string' && auth.key) {
|
|
12
|
+
return auth.key;
|
|
13
|
+
}
|
|
14
|
+
if (auth.type === 'wellknown') {
|
|
15
|
+
if (typeof auth.key === 'string' && auth.key)
|
|
16
|
+
return auth.key;
|
|
17
|
+
if (typeof auth.token === 'string' && auth.token)
|
|
18
|
+
return auth.token;
|
|
19
|
+
}
|
|
20
|
+
if (auth.type === 'oauth' && typeof auth.access === 'string' && auth.access) {
|
|
21
|
+
return auth.access;
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
function parseBaseURL(value) {
|
|
26
|
+
const normalized = sanitizeBaseURL(value);
|
|
27
|
+
if (!normalized)
|
|
28
|
+
return undefined;
|
|
29
|
+
try {
|
|
30
|
+
return new URL(normalized);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function isZhipuCodingBaseURL(value) {
|
|
37
|
+
const parsed = parseBaseURL(value);
|
|
38
|
+
if (!parsed || parsed.protocol !== 'https:')
|
|
39
|
+
return false;
|
|
40
|
+
const pathname = parsed.pathname.replace(/\/+$/, '');
|
|
41
|
+
const isKnownHost = parsed.host === 'open.bigmodel.cn' || parsed.host === 'api.z.ai';
|
|
42
|
+
if (!isKnownHost)
|
|
43
|
+
return false;
|
|
44
|
+
return pathname === '/api/anthropic' || pathname === '/api/coding/paas/v4';
|
|
45
|
+
}
|
|
46
|
+
function quotaUrl(baseURL) {
|
|
47
|
+
const parsed = parseBaseURL(baseURL);
|
|
48
|
+
if (parsed?.host === 'api.z.ai')
|
|
49
|
+
return ZHIPU_INTL_QUOTA_URL;
|
|
50
|
+
return ZHIPU_QUOTA_URL;
|
|
51
|
+
}
|
|
52
|
+
function normalizeUsedPercent(value) {
|
|
53
|
+
const numeric = asNumber(value);
|
|
54
|
+
if (numeric === undefined || !Number.isFinite(numeric))
|
|
55
|
+
return undefined;
|
|
56
|
+
if (numeric < 0)
|
|
57
|
+
return 0;
|
|
58
|
+
if (numeric > 100)
|
|
59
|
+
return 100;
|
|
60
|
+
return numeric;
|
|
61
|
+
}
|
|
62
|
+
function tokenWindowLabel(unit, count) {
|
|
63
|
+
const unitValue = asNumber(unit);
|
|
64
|
+
const countValue = asNumber(count);
|
|
65
|
+
if (unitValue === 3 && countValue && countValue > 0) {
|
|
66
|
+
return `${Math.round(countValue)}h`;
|
|
67
|
+
}
|
|
68
|
+
if (unitValue === 1 && countValue === 7)
|
|
69
|
+
return 'Weekly';
|
|
70
|
+
if (unitValue === 1 && countValue && countValue > 0) {
|
|
71
|
+
return `${Math.round(countValue)}d`;
|
|
72
|
+
}
|
|
73
|
+
if (unitValue === 5 && countValue && countValue > 0) {
|
|
74
|
+
return `${Math.round(countValue)}m`;
|
|
75
|
+
}
|
|
76
|
+
return 'Tokens';
|
|
77
|
+
}
|
|
78
|
+
function formatCountValue(value) {
|
|
79
|
+
if (!Number.isFinite(value))
|
|
80
|
+
return '0';
|
|
81
|
+
return Number.isInteger(value) ? String(value) : value.toFixed(1);
|
|
82
|
+
}
|
|
83
|
+
function parseTokenWindow(value) {
|
|
84
|
+
if (value.type !== 'TOKENS_LIMIT')
|
|
85
|
+
return undefined;
|
|
86
|
+
const usedPercent = normalizeUsedPercent(value.percentage);
|
|
87
|
+
if (usedPercent === undefined)
|
|
88
|
+
return undefined;
|
|
89
|
+
return {
|
|
90
|
+
label: tokenWindowLabel(value.unit, value.number),
|
|
91
|
+
remainingPercent: 100 - usedPercent,
|
|
92
|
+
usedPercent,
|
|
93
|
+
resetAt: toIso(value.nextResetTime),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async function fetchZhipuCodingPlanQuota({ providerID, providerOptions, auth, config, }) {
|
|
97
|
+
const checkedAt = Date.now();
|
|
98
|
+
const base = {
|
|
99
|
+
providerID,
|
|
100
|
+
adapterID: 'zhipuai-coding-plan',
|
|
101
|
+
label: 'Zhipu Coding Plan',
|
|
102
|
+
shortLabel: 'Zhipu',
|
|
103
|
+
sortOrder: 16,
|
|
104
|
+
};
|
|
105
|
+
const apiKey = resolveApiKey(auth, providerOptions);
|
|
106
|
+
if (!apiKey) {
|
|
107
|
+
return {
|
|
108
|
+
...base,
|
|
109
|
+
status: 'unavailable',
|
|
110
|
+
checkedAt,
|
|
111
|
+
note: 'missing api key',
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const response = await fetchWithTimeout(quotaUrl(providerOptions?.baseURL), {
|
|
115
|
+
method: 'GET',
|
|
116
|
+
headers: {
|
|
117
|
+
Accept: 'application/json',
|
|
118
|
+
Authorization: apiKey,
|
|
119
|
+
'Content-Type': 'application/json',
|
|
120
|
+
'User-Agent': 'opencode-quota-sidebar',
|
|
121
|
+
},
|
|
122
|
+
}, config.quota.requestTimeoutMs).catch(swallow('fetchZhipuCodingPlanQuota:usage'));
|
|
123
|
+
if (!response) {
|
|
124
|
+
return {
|
|
125
|
+
...base,
|
|
126
|
+
status: 'error',
|
|
127
|
+
checkedAt,
|
|
128
|
+
note: 'network request failed',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (!response.ok) {
|
|
132
|
+
return {
|
|
133
|
+
...base,
|
|
134
|
+
status: 'error',
|
|
135
|
+
checkedAt,
|
|
136
|
+
note: `http ${response.status}`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const payload = await response
|
|
140
|
+
.json()
|
|
141
|
+
.catch(swallow('fetchZhipuCodingPlanQuota:json'));
|
|
142
|
+
if (!isRecord(payload)) {
|
|
143
|
+
return {
|
|
144
|
+
...base,
|
|
145
|
+
status: 'error',
|
|
146
|
+
checkedAt,
|
|
147
|
+
note: 'invalid response',
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (payload.success !== true || asNumber(payload.code) !== 200) {
|
|
151
|
+
return {
|
|
152
|
+
...base,
|
|
153
|
+
status: 'error',
|
|
154
|
+
checkedAt,
|
|
155
|
+
note: typeof payload.msg === 'string' && payload.msg
|
|
156
|
+
? payload.msg
|
|
157
|
+
: 'quota request failed',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const data = isRecord(payload.data) ? payload.data : undefined;
|
|
161
|
+
const level = typeof data?.level === 'string' && data.level
|
|
162
|
+
? `${data.level.toUpperCase()} plan`
|
|
163
|
+
: undefined;
|
|
164
|
+
const limits = Array.isArray(data?.limits)
|
|
165
|
+
? data.limits.filter((item) => isRecord(item))
|
|
166
|
+
: [];
|
|
167
|
+
const token = limits
|
|
168
|
+
.map((item) => parseTokenWindow(item))
|
|
169
|
+
.find((value) => Boolean(value));
|
|
170
|
+
const windows = [token].filter((value) => Boolean(value));
|
|
171
|
+
const primary = token || windows[0];
|
|
172
|
+
return {
|
|
173
|
+
...base,
|
|
174
|
+
status: primary ? 'ok' : 'error',
|
|
175
|
+
checkedAt,
|
|
176
|
+
remainingPercent: primary?.remainingPercent,
|
|
177
|
+
resetAt: primary?.resetAt,
|
|
178
|
+
note: primary ? level : 'missing quota fields',
|
|
179
|
+
windows: windows.length > 0 ? windows : undefined,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
export const zhipuCodingPlanAdapter = {
|
|
183
|
+
id: 'zhipuai-coding-plan',
|
|
184
|
+
label: 'Zhipu Coding Plan',
|
|
185
|
+
shortLabel: 'Zhipu',
|
|
186
|
+
sortOrder: 16,
|
|
187
|
+
normalizeID: (providerID) => providerID === 'zhipuai-coding-plan' ? 'zhipuai-coding-plan' : undefined,
|
|
188
|
+
matchScore: ({ providerID, providerOptions }) => {
|
|
189
|
+
if (providerID === 'zhipuai-coding-plan')
|
|
190
|
+
return 100;
|
|
191
|
+
return isZhipuCodingBaseURL(providerOptions?.baseURL) ? 95 : 0;
|
|
192
|
+
},
|
|
193
|
+
isEnabled: (config) => configuredProviderEnabled(config.quota, 'zhipuai-coding-plan', true),
|
|
194
|
+
fetch: fetchZhipuCodingPlanQuota,
|
|
195
|
+
};
|
|
@@ -3,9 +3,10 @@ import { buzzAdapter } from './third_party/buzz.js';
|
|
|
3
3
|
import { copilotAdapter } from './core/copilot.js';
|
|
4
4
|
import { kimiForCodingAdapter } from './core/kimi_for_coding.js';
|
|
5
5
|
import { openaiAdapter } from './core/openai.js';
|
|
6
|
+
import { zhipuCodingPlanAdapter } from './core/zhipu_coding_plan.js';
|
|
6
7
|
import { QuotaProviderRegistry } from './registry.js';
|
|
7
8
|
import { rightCodeAdapter } from './third_party/rightcode.js';
|
|
8
9
|
import { xyaiVibeAdapter } from './third_party/xyai_vibe.js';
|
|
9
10
|
export declare function createDefaultProviderRegistry(): QuotaProviderRegistry;
|
|
10
|
-
export { anthropicAdapter, buzzAdapter, copilotAdapter, kimiForCodingAdapter, openaiAdapter, rightCodeAdapter, xyaiVibeAdapter, QuotaProviderRegistry, };
|
|
11
|
+
export { anthropicAdapter, buzzAdapter, copilotAdapter, kimiForCodingAdapter, openaiAdapter, rightCodeAdapter, xyaiVibeAdapter, zhipuCodingPlanAdapter, QuotaProviderRegistry, };
|
|
11
12
|
export type { AuthUpdate, AuthValue, ProviderResolveContext, QuotaFetchContext, QuotaProviderAdapter, RefreshedOAuthAuth, } from './types.js';
|
package/dist/providers/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { buzzAdapter } from './third_party/buzz.js';
|
|
|
3
3
|
import { copilotAdapter } from './core/copilot.js';
|
|
4
4
|
import { kimiForCodingAdapter } from './core/kimi_for_coding.js';
|
|
5
5
|
import { openaiAdapter } from './core/openai.js';
|
|
6
|
+
import { zhipuCodingPlanAdapter } from './core/zhipu_coding_plan.js';
|
|
6
7
|
import { QuotaProviderRegistry } from './registry.js';
|
|
7
8
|
import { rightCodeAdapter } from './third_party/rightcode.js';
|
|
8
9
|
import { xyaiVibeAdapter } from './third_party/xyai_vibe.js';
|
|
@@ -12,9 +13,10 @@ export function createDefaultProviderRegistry() {
|
|
|
12
13
|
registry.register(buzzAdapter);
|
|
13
14
|
registry.register(xyaiVibeAdapter);
|
|
14
15
|
registry.register(kimiForCodingAdapter);
|
|
16
|
+
registry.register(zhipuCodingPlanAdapter);
|
|
15
17
|
registry.register(openaiAdapter);
|
|
16
18
|
registry.register(copilotAdapter);
|
|
17
19
|
registry.register(anthropicAdapter);
|
|
18
20
|
return registry;
|
|
19
21
|
}
|
|
20
|
-
export { anthropicAdapter, buzzAdapter, copilotAdapter, kimiForCodingAdapter, openaiAdapter, rightCodeAdapter, xyaiVibeAdapter, QuotaProviderRegistry, };
|
|
22
|
+
export { anthropicAdapter, buzzAdapter, copilotAdapter, kimiForCodingAdapter, openaiAdapter, rightCodeAdapter, xyaiVibeAdapter, zhipuCodingPlanAdapter, QuotaProviderRegistry, };
|
package/dist/quota.js
CHANGED
|
@@ -33,7 +33,13 @@ export function quotaSort(left, right) {
|
|
|
33
33
|
}
|
|
34
34
|
export function listDefaultQuotaProviderIDs() {
|
|
35
35
|
// Keep default report behavior stable for built-in subscription providers.
|
|
36
|
-
return [
|
|
36
|
+
return [
|
|
37
|
+
'openai',
|
|
38
|
+
'kimi-for-coding',
|
|
39
|
+
'zhipuai-coding-plan',
|
|
40
|
+
'github-copilot',
|
|
41
|
+
'anthropic',
|
|
42
|
+
];
|
|
37
43
|
}
|
|
38
44
|
export function createQuotaRuntime() {
|
|
39
45
|
const providerRegistry = createDefaultProviderRegistry();
|
package/dist/quota_render.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const PROVIDER_SHORT_LABELS = {
|
|
2
2
|
openai: 'OpenAI',
|
|
3
3
|
'kimi-for-coding': 'Kimi',
|
|
4
|
+
'zhipuai-coding-plan': 'Zhipu',
|
|
4
5
|
'github-copilot': 'Copilot',
|
|
5
6
|
anthropic: 'Anthropic',
|
|
6
7
|
rightcode: 'RC',
|
|
@@ -8,6 +9,8 @@ const PROVIDER_SHORT_LABELS = {
|
|
|
8
9
|
export function canonicalProviderID(providerID) {
|
|
9
10
|
if (providerID.startsWith('github-copilot'))
|
|
10
11
|
return 'github-copilot';
|
|
12
|
+
if (providerID === 'zhipuai-coding-plan')
|
|
13
|
+
return 'zhipuai-coding-plan';
|
|
11
14
|
return providerID;
|
|
12
15
|
}
|
|
13
16
|
export function displayShortLabel(providerID) {
|
package/dist/storage_parse.js
CHANGED
|
@@ -177,6 +177,7 @@ export function parseQuotaCache(value) {
|
|
|
177
177
|
resetLabel: typeof window.resetLabel === 'string'
|
|
178
178
|
? window.resetLabel
|
|
179
179
|
: undefined,
|
|
180
|
+
note: typeof window.note === 'string' ? window.note : undefined,
|
|
180
181
|
remainingPercent: typeof window.remainingPercent === 'number'
|
|
181
182
|
? window.remainingPercent
|
|
182
183
|
: undefined,
|
package/dist/types.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export type QuotaWindow = {
|
|
|
5
5
|
showPercent?: boolean;
|
|
6
6
|
/** Prefix for reset/expiry time text in sidebar (default: Rst). */
|
|
7
7
|
resetLabel?: string;
|
|
8
|
+
/** Optional detail note rendered inline for the first window in reports. */
|
|
9
|
+
note?: string;
|
|
8
10
|
remainingPercent?: number;
|
|
9
11
|
usedPercent?: number;
|
|
10
12
|
resetAt?: string;
|
package/dist/usage.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { CacheCoverageMetrics, CacheCoverageMode, CacheUsageBuckets, Cached
|
|
|
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 = 9;
|
|
10
10
|
export type ProviderUsage = {
|
|
11
11
|
providerID: string;
|
|
12
12
|
input: number;
|
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 = 9;
|
|
8
8
|
const MAX_RECENT_PROVIDER_EVENTS = 100;
|
|
9
9
|
function emptyCacheUsageBucket() {
|
|
10
10
|
return {
|
package/dist/usage_service.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TtlValueCache } from './cache.js';
|
|
2
|
-
import { API_COST_ENABLED_PROVIDERS, cacheCoverageModeFromRates, calcEquivalentApiCostForMessage, canonicalApiCostProviderID, getBundledModelCostMap, modelCostLookupKeys, modelCostKey, parseModelCostRates, } from './cost.js';
|
|
2
|
+
import { API_COST_ENABLED_PROVIDERS, cacheCoverageModeFromRates, calcEquivalentApiCostForMessage, canonicalApiCostProviderID, canonicalPricingProviderID, getBundledModelCostMap, modelCostLookupKeys, modelCostKey, parseModelCostRates, } from './cost.js';
|
|
3
3
|
import { deleteSessionFromDayChunk, dateKeyFromTimestamp, scanAllSessions, updateSessionsInDayChunks, } from './storage.js';
|
|
4
4
|
import { periodStart } from './period.js';
|
|
5
5
|
import { debug, debugError, isRecord, mapConcurrent, swallow, } from './helpers.js';
|
|
@@ -60,7 +60,7 @@ export function createUsageService(deps) {
|
|
|
60
60
|
const rawProviderID = typeof provider.id === 'string' ? provider.id : undefined;
|
|
61
61
|
if (!rawProviderID)
|
|
62
62
|
return acc;
|
|
63
|
-
const canonicalProviderID =
|
|
63
|
+
const canonicalProviderID = canonicalPricingProviderID(rawProviderID);
|
|
64
64
|
const models = provider.models;
|
|
65
65
|
if (!isRecord(models))
|
|
66
66
|
return acc;
|
|
@@ -313,6 +313,42 @@ export function createUsageService(deps) {
|
|
|
313
313
|
return false;
|
|
314
314
|
return cached.billingVersion === USAGE_BILLING_CACHE_VERSION;
|
|
315
315
|
};
|
|
316
|
+
const hasAnySubscriptionProvider = (cached) => {
|
|
317
|
+
const providerIDs = Object.keys(cached.providers);
|
|
318
|
+
if (providerIDs.length === 0)
|
|
319
|
+
return true;
|
|
320
|
+
return providerIDs.some((providerID) => {
|
|
321
|
+
const canonical = canonicalApiCostProviderID(providerID);
|
|
322
|
+
return API_COST_ENABLED_PROVIDERS.has(canonical);
|
|
323
|
+
});
|
|
324
|
+
};
|
|
325
|
+
const shouldRecomputeUsageCache = (cached, hasPricing, hasResolvableApiCostMessage) => {
|
|
326
|
+
if (!isUsageBillingCurrent(cached))
|
|
327
|
+
return true;
|
|
328
|
+
if (!hasPricing)
|
|
329
|
+
return false;
|
|
330
|
+
if (!hasResolvableApiCostMessage)
|
|
331
|
+
return false;
|
|
332
|
+
if (cached.assistantMessages <= 0)
|
|
333
|
+
return false;
|
|
334
|
+
if (cached.apiCost > 0)
|
|
335
|
+
return false;
|
|
336
|
+
if (cached.total <= 0)
|
|
337
|
+
return false;
|
|
338
|
+
if (!hasAnySubscriptionProvider(cached))
|
|
339
|
+
return false;
|
|
340
|
+
return true;
|
|
341
|
+
};
|
|
342
|
+
const hasResolvableApiCostMessages = (entries, modelCostMap) => {
|
|
343
|
+
return entries.some(({ info }) => {
|
|
344
|
+
if (info.role !== 'assistant')
|
|
345
|
+
return false;
|
|
346
|
+
const providerID = canonicalApiCostProviderID(info.providerID);
|
|
347
|
+
if (!API_COST_ENABLED_PROVIDERS.has(providerID))
|
|
348
|
+
return false;
|
|
349
|
+
return modelCostLookupKeys(info.providerID, info.modelID).some((key) => Boolean(modelCostMap[key]));
|
|
350
|
+
});
|
|
351
|
+
};
|
|
316
352
|
const summarizeSessionUsage = async (sessionID, generationAtStart, options) => {
|
|
317
353
|
const load = await loadSessionEntries(sessionID);
|
|
318
354
|
const entries = load.status === 'ok' ? load.entries : undefined;
|
|
@@ -334,14 +370,23 @@ export function createUsageService(deps) {
|
|
|
334
370
|
return { usage: empty, persist: false };
|
|
335
371
|
}
|
|
336
372
|
const modelCostMap = await getModelCostMap();
|
|
373
|
+
const hasPricing = Object.keys(modelCostMap).length > 0;
|
|
374
|
+
const hasResolvablePricing = hasResolvableApiCostMessages(entries, modelCostMap);
|
|
337
375
|
const staleBillingCache = Boolean(sessionState?.usage) &&
|
|
338
376
|
!isUsageBillingCurrent(sessionState?.usage);
|
|
339
|
-
const
|
|
377
|
+
const pricingRefreshCache = sessionState?.usage &&
|
|
378
|
+
shouldRecomputeUsageCache(sessionState.usage, hasPricing, hasResolvablePricing);
|
|
379
|
+
const forceRescan = forceRescanSessions.has(sessionID) ||
|
|
380
|
+
staleBillingCache ||
|
|
381
|
+
Boolean(pricingRefreshCache);
|
|
340
382
|
if (forceRescan)
|
|
341
383
|
forceRescanSessions.delete(sessionID);
|
|
342
384
|
if (staleBillingCache) {
|
|
343
385
|
debug(`usage cache billing refresh for session ${sessionID}`);
|
|
344
386
|
}
|
|
387
|
+
if (pricingRefreshCache && !staleBillingCache) {
|
|
388
|
+
debug(`usage cache pricing refresh for session ${sessionID}`);
|
|
389
|
+
}
|
|
345
390
|
const { usage, cursor } = summarizeMessagesIncremental(entries, sessionState?.usage, sessionState?.cursor, forceRescan, {
|
|
346
391
|
calcApiCost: (message) => calcEquivalentApiCost(message, modelCostMap),
|
|
347
392
|
classifyCacheMode: (message) => classifyCacheMode(message, modelCostMap),
|
|
@@ -450,32 +495,6 @@ export function createUsageService(deps) {
|
|
|
450
495
|
const usage = emptyUsageSummary();
|
|
451
496
|
const modelCostMap = await getModelCostMap();
|
|
452
497
|
const hasPricing = Object.keys(modelCostMap).length > 0;
|
|
453
|
-
const hasAnySubscriptionProvider = (cached) => {
|
|
454
|
-
const providerIDs = Object.keys(cached.providers);
|
|
455
|
-
// Back-compat: older cached chunks may have empty providers.
|
|
456
|
-
// In that case, allow recompute so we can persist apiCost.
|
|
457
|
-
if (providerIDs.length === 0)
|
|
458
|
-
return true;
|
|
459
|
-
return providerIDs.some((providerID) => {
|
|
460
|
-
const canonical = canonicalApiCostProviderID(providerID);
|
|
461
|
-
return API_COST_ENABLED_PROVIDERS.has(canonical);
|
|
462
|
-
});
|
|
463
|
-
};
|
|
464
|
-
const shouldRecomputeUsageCache = (cached) => {
|
|
465
|
-
if (!isUsageBillingCurrent(cached))
|
|
466
|
-
return true;
|
|
467
|
-
if (!hasPricing)
|
|
468
|
-
return false;
|
|
469
|
-
if (cached.assistantMessages <= 0)
|
|
470
|
-
return false;
|
|
471
|
-
if (cached.apiCost > 0)
|
|
472
|
-
return false;
|
|
473
|
-
if (cached.total <= 0)
|
|
474
|
-
return false;
|
|
475
|
-
if (!hasAnySubscriptionProvider(cached))
|
|
476
|
-
return false;
|
|
477
|
-
return true;
|
|
478
|
-
};
|
|
479
498
|
if (sessions.length > 0) {
|
|
480
499
|
const fetched = await mapConcurrent(sessions, RANGE_USAGE_CONCURRENCY, async (session) => {
|
|
481
500
|
const load = await loadSessionEntries(session.sessionID);
|
|
@@ -500,7 +519,7 @@ export function createUsageService(deps) {
|
|
|
500
519
|
classifyCacheMode: (message) => classifyCacheMode(message, modelCostMap),
|
|
501
520
|
});
|
|
502
521
|
const shouldPersistFullUsage = !session.state.usage ||
|
|
503
|
-
shouldRecomputeUsageCache(session.state.usage);
|
|
522
|
+
shouldRecomputeUsageCache(session.state.usage, hasPricing, hasResolvableApiCostMessages(entries, modelCostMap));
|
|
504
523
|
if (!shouldPersistFullUsage) {
|
|
505
524
|
return {
|
|
506
525
|
sessionID: session.sessionID,
|