@chenlongapps/opencode-token-usage 0.4.2 → 0.4.4
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 +16 -5
- package/README.zh-CN.md +16 -5
- package/ROADMAP.md +4 -1
- package/dist/context-capture.d.ts +43 -0
- package/dist/context-capture.js +111 -0
- package/dist/controller.d.ts +4 -0
- package/dist/controller.js +43 -15
- package/dist/index.js +29 -8
- package/dist/performance.d.ts +19 -0
- package/dist/performance.js +112 -36
- package/dist/prices.generated.d.ts +338 -15
- package/dist/prices.generated.js +32 -10
- package/dist/pricing.d.ts +1 -1
- package/dist/source.d.ts +3 -2
- package/dist/source.js +31 -16
- package/dist/tui.js +209 -153
- package/dist/usage.d.ts +22 -2
- package/dist/usage.js +91 -14
- package/docs/pricing.md +1 -0
- package/docs/verification.md +54 -0
- package/package.json +2 -2
package/dist/performance.js
CHANGED
|
@@ -1,4 +1,31 @@
|
|
|
1
1
|
import { normalize } from "./usage.js";
|
|
2
|
+
export const LIVE_WINDOW_MS = 2_000;
|
|
3
|
+
export const LIVE_EWMA_ALPHA = 0.35;
|
|
4
|
+
export const LIVE_BYTES_PER_TOKEN = 4;
|
|
5
|
+
/** Raw live throughput from one step's recent observable deltas. */
|
|
6
|
+
export function estimatedLiveTps(samples) {
|
|
7
|
+
if (samples.length < 2)
|
|
8
|
+
return undefined;
|
|
9
|
+
const first = samples[0];
|
|
10
|
+
const last = samples[samples.length - 1];
|
|
11
|
+
const duration = last.time - first.time;
|
|
12
|
+
if (!Number.isFinite(duration) || duration <= 0)
|
|
13
|
+
return undefined;
|
|
14
|
+
let bytes = 0;
|
|
15
|
+
for (const sample of samples) {
|
|
16
|
+
if (!Number.isFinite(sample.time) || !Number.isFinite(sample.bytes) || sample.bytes < 0)
|
|
17
|
+
return undefined;
|
|
18
|
+
bytes += sample.bytes;
|
|
19
|
+
}
|
|
20
|
+
if (bytes <= 0)
|
|
21
|
+
return undefined;
|
|
22
|
+
return (bytes / LIVE_BYTES_PER_TOKEN) / (duration / 1_000);
|
|
23
|
+
}
|
|
24
|
+
export function smoothLiveTps(previous, raw) {
|
|
25
|
+
if (previous === undefined || !Number.isFinite(previous))
|
|
26
|
+
return raw;
|
|
27
|
+
return previous * (1 - LIVE_EWMA_ALPHA) + raw * LIVE_EWMA_ALPHA;
|
|
28
|
+
}
|
|
2
29
|
const finite = (value) => typeof value === "number" && Number.isFinite(value);
|
|
3
30
|
function historicalFirst(message) {
|
|
4
31
|
const started = message.time?.created;
|
|
@@ -25,20 +52,18 @@ function historicalFirst(message) {
|
|
|
25
52
|
}
|
|
26
53
|
/** Exact completed-run TPS and the average of only measurable TTFT samples. */
|
|
27
54
|
export function historicalPerformance(messages, observations = []) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
55
|
+
return performanceWithObservations(preparePerformance(messages), observations);
|
|
56
|
+
}
|
|
57
|
+
/** The immutable part of a snapshot is scanned once, then reused for stream updates. */
|
|
58
|
+
export function preparePerformance(messages) {
|
|
33
59
|
let output = 0;
|
|
34
60
|
let duration = 0;
|
|
35
|
-
let
|
|
36
|
-
let
|
|
37
|
-
const
|
|
61
|
+
let ttftSum = 0;
|
|
62
|
+
let ttftCount = 0;
|
|
63
|
+
const historical = new Map();
|
|
38
64
|
for (const message of messages) {
|
|
39
65
|
if (message.type !== "assistant")
|
|
40
66
|
continue;
|
|
41
|
-
messagesSeen.add(message.id);
|
|
42
67
|
const created = message.time?.created;
|
|
43
68
|
const streamed = message.time?.streamed;
|
|
44
69
|
const tokens = normalize(message.tokens);
|
|
@@ -47,23 +72,35 @@ export function historicalPerformance(messages, observations = []) {
|
|
|
47
72
|
output += generated;
|
|
48
73
|
duration += streamed - created;
|
|
49
74
|
}
|
|
50
|
-
const
|
|
51
|
-
const sample = runtime ? runtime.first - runtime.started : historicalFirst(message);
|
|
75
|
+
const sample = historicalFirst(message);
|
|
52
76
|
if (sample !== undefined && finite(sample) && sample >= 0) {
|
|
53
|
-
|
|
54
|
-
|
|
77
|
+
historical.set(message.id, sample);
|
|
78
|
+
ttftSum += sample;
|
|
79
|
+
ttftCount++;
|
|
55
80
|
}
|
|
56
81
|
}
|
|
57
|
-
|
|
82
|
+
return { ...(duration > 0 ? { tps: output / (duration / 1_000) } : {}), ttftSum, ttftCount, historical };
|
|
83
|
+
}
|
|
84
|
+
function performanceWithObservations(prepared, observations) {
|
|
85
|
+
const observed = new Map();
|
|
86
|
+
for (const sample of observations) {
|
|
87
|
+
if (finite(sample.started) && finite(sample.first) && sample.first >= sample.started)
|
|
88
|
+
observed.set(sample.messageID, sample);
|
|
89
|
+
}
|
|
90
|
+
let ttft = prepared.ttftSum;
|
|
91
|
+
let ttftSamples = prepared.ttftCount;
|
|
58
92
|
for (const sample of observed.values()) {
|
|
59
|
-
|
|
60
|
-
|
|
93
|
+
const old = prepared.historical.get(sample.messageID);
|
|
94
|
+
if (old !== undefined) {
|
|
95
|
+
ttft -= old;
|
|
96
|
+
ttftSamples--;
|
|
97
|
+
}
|
|
61
98
|
ttft += sample.first - sample.started;
|
|
62
99
|
ttftSamples++;
|
|
63
100
|
}
|
|
64
101
|
const result = {};
|
|
65
|
-
if (
|
|
66
|
-
result.tps =
|
|
102
|
+
if (prepared.tps !== undefined)
|
|
103
|
+
result.tps = prepared.tps;
|
|
67
104
|
if (ttftSamples > 0)
|
|
68
105
|
result.ttft = ttft / ttftSamples;
|
|
69
106
|
return result;
|
|
@@ -117,7 +154,8 @@ export class PerformanceMonitor {
|
|
|
117
154
|
const now = this.now();
|
|
118
155
|
const changed = new Set();
|
|
119
156
|
for (const [key, step] of this.active) {
|
|
120
|
-
if (step.finishedAt
|
|
157
|
+
if ((step.finishedAt ?? step.reconciledAt) !== undefined
|
|
158
|
+
&& (step.finishedAt ?? step.reconciledAt) + this.retention <= now) {
|
|
121
159
|
this.active.delete(key);
|
|
122
160
|
changed.add(step.sessionID);
|
|
123
161
|
}
|
|
@@ -136,8 +174,9 @@ export class PerformanceMonitor {
|
|
|
136
174
|
return;
|
|
137
175
|
let next = Infinity;
|
|
138
176
|
for (const step of this.active.values()) {
|
|
139
|
-
|
|
140
|
-
|
|
177
|
+
const settled = step.finishedAt ?? step.reconciledAt;
|
|
178
|
+
if (settled !== undefined)
|
|
179
|
+
next = Math.min(next, settled + this.retention);
|
|
141
180
|
}
|
|
142
181
|
for (const sample of this.observed.values()) {
|
|
143
182
|
if (sample.expiresAt !== undefined)
|
|
@@ -175,7 +214,7 @@ export class PerformanceMonitor {
|
|
|
175
214
|
if (this.active.has(key))
|
|
176
215
|
return false;
|
|
177
216
|
this.active.set(key, {
|
|
178
|
-
messageID, sessionID, started: data.started, bytes: 0, events: new Set(),
|
|
217
|
+
messageID, sessionID, started: data.started, bytes: 0, samples: [], events: new Set(),
|
|
179
218
|
});
|
|
180
219
|
return false;
|
|
181
220
|
}
|
|
@@ -195,6 +234,8 @@ export class PerformanceMonitor {
|
|
|
195
234
|
const sample = this.observed.get(key);
|
|
196
235
|
if (sample)
|
|
197
236
|
sample.expiresAt = step.finishedAt + this.retention;
|
|
237
|
+
if (step.reconciledAt !== undefined)
|
|
238
|
+
this.active.delete(key);
|
|
198
239
|
this.scheduleCleanup();
|
|
199
240
|
return false;
|
|
200
241
|
}
|
|
@@ -207,7 +248,20 @@ export class PerformanceMonitor {
|
|
|
207
248
|
return false;
|
|
208
249
|
if (event.id)
|
|
209
250
|
step.events.add(event.id);
|
|
210
|
-
step.
|
|
251
|
+
if (step.reconciledAt !== undefined) {
|
|
252
|
+
// A later delta means the snapshot did not represent the final stream.
|
|
253
|
+
delete step.reconciledAt;
|
|
254
|
+
const sample = this.observed.get(key);
|
|
255
|
+
if (sample)
|
|
256
|
+
delete sample.expiresAt;
|
|
257
|
+
this.scheduleCleanup();
|
|
258
|
+
}
|
|
259
|
+
const sampleBytes = new TextEncoder().encode(data.delta).byteLength;
|
|
260
|
+
step.bytes += sampleBytes;
|
|
261
|
+
step.samples.push({ time: event.created, bytes: sampleBytes });
|
|
262
|
+
const cutoff = event.created - LIVE_WINDOW_MS;
|
|
263
|
+
while (step.samples.length > 0 && step.samples[0].time < cutoff)
|
|
264
|
+
step.samples.shift();
|
|
211
265
|
step.last = Math.max(step.last ?? event.created, event.created);
|
|
212
266
|
if (step.first === undefined) {
|
|
213
267
|
step.first = event.created;
|
|
@@ -225,28 +279,50 @@ export class PerformanceMonitor {
|
|
|
225
279
|
represented.add(message.id);
|
|
226
280
|
}
|
|
227
281
|
for (const [key, step] of this.active) {
|
|
228
|
-
if (sessions.has(step.sessionID) && represented.has(step.messageID)
|
|
229
|
-
|
|
230
|
-
|
|
282
|
+
if (sessions.has(step.sessionID) && represented.has(step.messageID)) {
|
|
283
|
+
if (step.finishedAt !== undefined)
|
|
284
|
+
this.active.delete(key);
|
|
285
|
+
else {
|
|
286
|
+
// A snapshot can arrive before step.ended. Retain the step to attach
|
|
287
|
+
// the real expiry later, and set a fallback if that event is lost.
|
|
288
|
+
step.reconciledAt ??= this.now();
|
|
289
|
+
const sample = this.observed.get(key);
|
|
290
|
+
if (sample)
|
|
291
|
+
sample.expiresAt ??= step.reconciledAt + this.retention;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
231
294
|
}
|
|
295
|
+
this.scheduleCleanup();
|
|
232
296
|
}
|
|
233
297
|
summary(messages, sessionIDs) {
|
|
298
|
+
return this.summaryPrepared(preparePerformance(messages), sessionIDs);
|
|
299
|
+
}
|
|
300
|
+
summaryPrepared(prepared, sessionIDs) {
|
|
234
301
|
const sessions = new Set(sessionIDs);
|
|
235
302
|
const samples = [...this.observed.values()].filter(sample => sessions.has(sample.sessionID));
|
|
236
|
-
const result =
|
|
237
|
-
|
|
238
|
-
|
|
303
|
+
const result = performanceWithObservations(prepared, samples);
|
|
304
|
+
// Live throughput covers only observable deltas after the first one, over a
|
|
305
|
+
// short sliding window. TTFT keeps the started -> first wait separately, and
|
|
306
|
+
// completed provider usage still replaces this estimate via reconcile().
|
|
307
|
+
let weighted = 0;
|
|
308
|
+
let durationTotal = 0;
|
|
239
309
|
for (const step of this.active.values()) {
|
|
240
|
-
if (!sessions.has(step.sessionID) || step.
|
|
310
|
+
if (!sessions.has(step.sessionID) || step.reconciledAt !== undefined)
|
|
311
|
+
continue;
|
|
312
|
+
const raw = estimatedLiveTps(step.samples);
|
|
313
|
+
if (raw === undefined)
|
|
241
314
|
continue;
|
|
242
|
-
const
|
|
243
|
-
|
|
315
|
+
const first = step.samples[0];
|
|
316
|
+
const last = step.samples[step.samples.length - 1];
|
|
317
|
+
const duration = last.time - first.time;
|
|
318
|
+
if (!finite(duration) || duration <= 0)
|
|
244
319
|
continue;
|
|
245
|
-
|
|
246
|
-
|
|
320
|
+
step.liveTps = smoothLiveTps(step.liveTps, raw);
|
|
321
|
+
weighted += step.liveTps * duration;
|
|
322
|
+
durationTotal += duration;
|
|
247
323
|
}
|
|
248
|
-
if (
|
|
249
|
-
result.tps =
|
|
324
|
+
if (durationTotal > 0) {
|
|
325
|
+
result.tps = weighted / durationTotal;
|
|
250
326
|
result.tpsEstimated = true;
|
|
251
327
|
}
|
|
252
328
|
return result;
|