@chenlongapps/opencode-token-usage 0.4.8 → 0.4.10
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 +17 -10
- package/README.zh-CN.md +17 -10
- package/ROADMAP.md +9 -2
- package/dist/click.d.ts +9 -0
- package/dist/click.js +20 -0
- package/dist/context-rpc.d.ts +11 -0
- package/dist/context-rpc.js +6 -0
- package/dist/controller.d.ts +14 -1
- package/dist/controller.js +90 -7
- package/dist/index.js +1 -0
- package/dist/performance.d.ts +23 -5
- package/dist/performance.js +263 -51
- package/dist/prices.generated.d.ts +23 -1
- package/dist/prices.generated.js +3 -1
- package/dist/pricing.d.ts +1 -1
- package/dist/runtime-monitor.d.ts +34 -0
- package/dist/runtime-monitor.js +155 -0
- package/dist/runtime.d.ts +25 -3
- package/dist/runtime.js +44 -7
- package/dist/source.d.ts +3 -1
- package/dist/source.js +10 -0
- package/dist/tui.js +252 -203
- package/dist/usage.d.ts +3 -2
- package/dist/usage.js +16 -7
- package/docs/verification.md +180 -0
- package/package.json +1 -1
package/dist/performance.js
CHANGED
|
@@ -1,30 +1,41 @@
|
|
|
1
1
|
import { normalize } from "./usage.js";
|
|
2
2
|
export const LIVE_WINDOW_MS = 2_000;
|
|
3
|
+
export const LIVE_MIN_SPAN_MS = 500;
|
|
4
|
+
export const LIVE_SAMPLE_MS = 100;
|
|
3
5
|
export const LIVE_EWMA_ALPHA = 0.35;
|
|
4
6
|
export const LIVE_BYTES_PER_TOKEN = 4;
|
|
5
|
-
/**
|
|
7
|
+
/** Quiet streams need a server liveness check, not a maximum tool-wait duration. */
|
|
8
|
+
export const ACTIVITY_CHECK_MS = 60_000;
|
|
9
|
+
/** The first batch is a counter baseline, not bytes generated after its timestamp. */
|
|
6
10
|
export function estimatedLiveTps(samples) {
|
|
7
11
|
if (samples.length < 2)
|
|
8
12
|
return undefined;
|
|
9
13
|
const first = samples[0];
|
|
10
14
|
const last = samples[samples.length - 1];
|
|
11
15
|
const duration = last.time - first.time;
|
|
12
|
-
if (!Number.isFinite(duration) || duration
|
|
16
|
+
if (!Number.isFinite(duration) || duration < LIVE_MIN_SPAN_MS)
|
|
13
17
|
return undefined;
|
|
14
18
|
let bytes = 0;
|
|
19
|
+
let time = first.time;
|
|
15
20
|
for (const sample of samples) {
|
|
16
|
-
if (!Number.isFinite(sample.time) || !Number.isFinite(sample.bytes) || sample.bytes < 0)
|
|
21
|
+
if (!Number.isFinite(sample.time) || sample.time < time || !Number.isFinite(sample.bytes) || sample.bytes < 0)
|
|
17
22
|
return undefined;
|
|
18
|
-
|
|
23
|
+
if (sample.time > first.time)
|
|
24
|
+
bytes += sample.bytes;
|
|
25
|
+
time = sample.time;
|
|
19
26
|
}
|
|
20
|
-
if (bytes <= 0)
|
|
27
|
+
if (!Number.isFinite(bytes) || bytes <= 0)
|
|
21
28
|
return undefined;
|
|
22
29
|
return (bytes / LIVE_BYTES_PER_TOKEN) / (duration / 1_000);
|
|
23
30
|
}
|
|
24
|
-
|
|
31
|
+
/** Time-based EWMA: elapsed time determines the weight, never delta count. */
|
|
32
|
+
export function smoothLiveTps(previous, raw, elapsed = LIVE_MIN_SPAN_MS) {
|
|
25
33
|
if (previous === undefined || !Number.isFinite(previous))
|
|
26
34
|
return raw;
|
|
27
|
-
|
|
35
|
+
if (!Number.isFinite(elapsed) || elapsed <= 0)
|
|
36
|
+
return previous;
|
|
37
|
+
const alpha = 1 - (1 - LIVE_EWMA_ALPHA) ** (elapsed / LIVE_MIN_SPAN_MS);
|
|
38
|
+
return previous * (1 - alpha) + raw * alpha;
|
|
28
39
|
}
|
|
29
40
|
const finite = (value) => typeof value === "number" && Number.isFinite(value);
|
|
30
41
|
function historicalFirst(message) {
|
|
@@ -107,17 +118,20 @@ function performanceWithObservations(prepared, observations) {
|
|
|
107
118
|
}
|
|
108
119
|
/** Captures streaming performance for every session for one plugin lifetime. */
|
|
109
120
|
export class PerformanceMonitor {
|
|
110
|
-
retention;
|
|
111
|
-
now;
|
|
112
121
|
active = new Map();
|
|
113
122
|
observed = new Map();
|
|
114
123
|
listeners = new Set();
|
|
115
124
|
cleanupTimer;
|
|
125
|
+
synchronization;
|
|
116
126
|
disposed = false;
|
|
117
127
|
unsubscribe;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
128
|
+
retention;
|
|
129
|
+
monotonic;
|
|
130
|
+
readActive;
|
|
131
|
+
constructor(subscribe, options = {}) {
|
|
132
|
+
this.retention = options.retention ?? 60_000;
|
|
133
|
+
this.monotonic = options.monotonic ?? (() => performance.now());
|
|
134
|
+
this.readActive = options.active;
|
|
121
135
|
this.unsubscribe = subscribe?.(event => this.handle(event));
|
|
122
136
|
}
|
|
123
137
|
listen(listener) {
|
|
@@ -131,6 +145,7 @@ export class PerformanceMonitor {
|
|
|
131
145
|
return;
|
|
132
146
|
this.disposed = true;
|
|
133
147
|
this.unsubscribe?.();
|
|
148
|
+
this.synchronization?.abort();
|
|
134
149
|
clearTimeout(this.cleanupTimer);
|
|
135
150
|
this.cleanupTimer = undefined;
|
|
136
151
|
this.listeners.clear();
|
|
@@ -149,19 +164,113 @@ export class PerformanceMonitor {
|
|
|
149
164
|
for (const [key, sample] of this.observed)
|
|
150
165
|
if (sample.sessionID === sessionID)
|
|
151
166
|
this.observed.delete(key);
|
|
167
|
+
this.scheduleCleanup();
|
|
168
|
+
}
|
|
169
|
+
resetLive(step) {
|
|
170
|
+
step.samples = [];
|
|
171
|
+
delete step.bucketStarted;
|
|
172
|
+
delete step.observedAt;
|
|
173
|
+
delete step.liveTps;
|
|
174
|
+
delete step.liveDuration;
|
|
175
|
+
delete step.smoothedAt;
|
|
176
|
+
}
|
|
177
|
+
touch(step, created) {
|
|
178
|
+
step.revision++;
|
|
179
|
+
step.checkedAt = this.monotonic();
|
|
180
|
+
if (finite(created))
|
|
181
|
+
step.latest = Math.max(step.latest, created);
|
|
182
|
+
// A live event wins over an inactive snapshot taken just before it arrived.
|
|
183
|
+
if (step.inactive) {
|
|
184
|
+
delete step.finishedAt;
|
|
185
|
+
delete step.inactive;
|
|
186
|
+
const sample = this.observed.get(this.key(step.sessionID, step.messageID));
|
|
187
|
+
if (sample)
|
|
188
|
+
delete sample.expiresAt;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
finish(step, inactive = false) {
|
|
192
|
+
if (step.finishedAt !== undefined && (inactive || !step.inactive))
|
|
193
|
+
return;
|
|
194
|
+
step.finishedAt ??= this.monotonic();
|
|
195
|
+
step.inactive = inactive;
|
|
196
|
+
step.revision++;
|
|
197
|
+
const key = this.key(step.sessionID, step.messageID);
|
|
198
|
+
const sample = this.observed.get(key);
|
|
199
|
+
if (sample)
|
|
200
|
+
sample.expiresAt = step.finishedAt + this.retention;
|
|
201
|
+
this.resetLive(step);
|
|
202
|
+
this.publish(step.sessionID);
|
|
203
|
+
}
|
|
204
|
+
/** One shared, history-free read covers offscreen sessions too. Failures are not idle. */
|
|
205
|
+
async checkActivity(force = false) {
|
|
206
|
+
if (this.disposed || !this.readActive)
|
|
207
|
+
return;
|
|
208
|
+
if (this.synchronization) {
|
|
209
|
+
if (!force)
|
|
210
|
+
return;
|
|
211
|
+
this.synchronization.abort();
|
|
212
|
+
this.synchronization = undefined;
|
|
213
|
+
}
|
|
214
|
+
const now = this.monotonic();
|
|
215
|
+
const unfinished = [...this.active.values()].filter(step => step.finishedAt === undefined);
|
|
216
|
+
if (!unfinished.length || (!force && !unfinished.some(step => now - step.checkedAt >= ACTIVITY_CHECK_MS)))
|
|
217
|
+
return;
|
|
218
|
+
// The endpoint is process-wide: use each read for every tracked unfinished step,
|
|
219
|
+
// rather than issuing one request per staggered session deadline.
|
|
220
|
+
const candidates = new Map(unfinished.map(step => [step, step.revision]));
|
|
221
|
+
const request = new AbortController();
|
|
222
|
+
this.synchronization = request;
|
|
223
|
+
const signal = AbortSignal.any([request.signal, AbortSignal.timeout(5_000)]);
|
|
224
|
+
let onAbort = () => { };
|
|
225
|
+
try {
|
|
226
|
+
// Also bound readers that fail to cooperate with cancellation.
|
|
227
|
+
const active = await Promise.race([
|
|
228
|
+
this.readActive(signal),
|
|
229
|
+
new Promise((_, reject) => {
|
|
230
|
+
onAbort = () => reject(signal.reason);
|
|
231
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
232
|
+
if (signal.aborted)
|
|
233
|
+
onAbort();
|
|
234
|
+
}),
|
|
235
|
+
]);
|
|
236
|
+
if (this.disposed || signal.aborted || this.synchronization !== request)
|
|
237
|
+
return;
|
|
238
|
+
for (const [step, revision] of candidates) {
|
|
239
|
+
if (this.active.get(this.key(step.sessionID, step.messageID)) !== step || step.revision !== revision)
|
|
240
|
+
continue;
|
|
241
|
+
if (!active.has(step.sessionID))
|
|
242
|
+
this.finish(step, true);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
catch { /* Preserve quiet tools/retries when server activity is unknown. */ }
|
|
246
|
+
finally {
|
|
247
|
+
signal.removeEventListener("abort", onAbort);
|
|
248
|
+
request.abort();
|
|
249
|
+
if (this.synchronization === request) {
|
|
250
|
+
this.synchronization = undefined;
|
|
251
|
+
for (const [step, revision] of candidates) {
|
|
252
|
+
if (step.revision === revision)
|
|
253
|
+
step.checkedAt = this.monotonic();
|
|
254
|
+
}
|
|
255
|
+
this.scheduleCleanup();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
152
258
|
}
|
|
153
259
|
prune() {
|
|
154
|
-
const
|
|
260
|
+
const monotonic = this.monotonic();
|
|
155
261
|
const changed = new Set();
|
|
156
262
|
for (const [key, step] of this.active) {
|
|
157
|
-
if (
|
|
158
|
-
&& (step.finishedAt ?? step.reconciledAt) + this.retention <= now) {
|
|
263
|
+
if (step.finishedAt !== undefined && step.finishedAt + this.retention <= monotonic) {
|
|
159
264
|
this.active.delete(key);
|
|
160
265
|
changed.add(step.sessionID);
|
|
161
266
|
}
|
|
267
|
+
else if (step.observedAt !== undefined && monotonic - step.observedAt >= LIVE_WINDOW_MS) {
|
|
268
|
+
this.resetLive(step);
|
|
269
|
+
changed.add(step.sessionID);
|
|
270
|
+
}
|
|
162
271
|
}
|
|
163
272
|
for (const [key, sample] of this.observed) {
|
|
164
|
-
if (sample.expiresAt !== undefined && sample.expiresAt <=
|
|
273
|
+
if (sample.expiresAt !== undefined && sample.expiresAt <= monotonic) {
|
|
165
274
|
this.observed.delete(key);
|
|
166
275
|
changed.add(sample.sessionID);
|
|
167
276
|
}
|
|
@@ -170,17 +279,25 @@ export class PerformanceMonitor {
|
|
|
170
279
|
}
|
|
171
280
|
scheduleCleanup() {
|
|
172
281
|
clearTimeout(this.cleanupTimer);
|
|
173
|
-
|
|
282
|
+
this.cleanupTimer = undefined;
|
|
283
|
+
if (this.disposed)
|
|
174
284
|
return;
|
|
175
285
|
let next = Infinity;
|
|
286
|
+
const monotonic = this.monotonic();
|
|
287
|
+
const retain = Number.isFinite(this.retention) && this.retention >= 0;
|
|
176
288
|
for (const step of this.active.values()) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
289
|
+
if (retain && step.finishedAt !== undefined)
|
|
290
|
+
next = Math.min(next, step.finishedAt + this.retention - monotonic);
|
|
291
|
+
if (step.finishedAt === undefined && this.readActive && !this.synchronization) {
|
|
292
|
+
next = Math.min(next, step.checkedAt + ACTIVITY_CHECK_MS - monotonic);
|
|
293
|
+
}
|
|
294
|
+
// Local monotonic age, never client epoch minus a remote event timestamp.
|
|
295
|
+
if (step.observedAt !== undefined)
|
|
296
|
+
next = Math.min(next, step.observedAt + LIVE_WINDOW_MS - monotonic);
|
|
180
297
|
}
|
|
181
298
|
for (const sample of this.observed.values()) {
|
|
182
|
-
if (sample.expiresAt !== undefined)
|
|
183
|
-
next = Math.min(next, sample.expiresAt);
|
|
299
|
+
if (retain && sample.expiresAt !== undefined)
|
|
300
|
+
next = Math.min(next, sample.expiresAt - monotonic);
|
|
184
301
|
}
|
|
185
302
|
if (!Number.isFinite(next))
|
|
186
303
|
return;
|
|
@@ -188,8 +305,9 @@ export class PerformanceMonitor {
|
|
|
188
305
|
this.cleanupTimer = undefined;
|
|
189
306
|
for (const sessionID of this.prune())
|
|
190
307
|
this.publish(sessionID);
|
|
308
|
+
void this.checkActivity();
|
|
191
309
|
this.scheduleCleanup();
|
|
192
|
-
}, Math.max(0,
|
|
310
|
+
}, Math.max(0, Math.ceil(next)));
|
|
193
311
|
this.cleanupTimer.unref?.();
|
|
194
312
|
}
|
|
195
313
|
handle(event) {
|
|
@@ -197,6 +315,16 @@ export class PerformanceMonitor {
|
|
|
197
315
|
return false;
|
|
198
316
|
for (const sessionID of this.prune())
|
|
199
317
|
this.publish(sessionID);
|
|
318
|
+
if (event.type === "server.connected") {
|
|
319
|
+
// Delivery across a connection gap cannot share a throughput window.
|
|
320
|
+
for (const step of this.active.values()) {
|
|
321
|
+
this.resetLive(step);
|
|
322
|
+
this.publish(step.sessionID);
|
|
323
|
+
}
|
|
324
|
+
void this.checkActivity(true);
|
|
325
|
+
this.scheduleCleanup();
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
200
328
|
const data = (event.data && typeof event.data === "object" ? event.data : {});
|
|
201
329
|
const sessionID = typeof data.sessionID === "string" ? data.sessionID : undefined;
|
|
202
330
|
if (event.type === "session.deleted") {
|
|
@@ -204,8 +332,30 @@ export class PerformanceMonitor {
|
|
|
204
332
|
this.deleteSession(sessionID);
|
|
205
333
|
return false;
|
|
206
334
|
}
|
|
335
|
+
if (!sessionID)
|
|
336
|
+
return false;
|
|
337
|
+
if (["session.execution.succeeded", "session.execution.failed", "session.execution.interrupted", "session.idle"].includes(event.type)
|
|
338
|
+
|| (event.type === "session.status" && data.status?.type === "idle")) {
|
|
339
|
+
for (const step of this.active.values()) {
|
|
340
|
+
if (step.sessionID !== sessionID || !finite(event.created) || event.created < step.latest)
|
|
341
|
+
continue;
|
|
342
|
+
step.latest = event.created;
|
|
343
|
+
this.finish(step);
|
|
344
|
+
}
|
|
345
|
+
this.scheduleCleanup();
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
if (event.type === "session.execution.started"
|
|
349
|
+
|| (event.type === "session.status" && ["busy", "retry"].includes(String(data.status?.type)))) {
|
|
350
|
+
for (const step of this.active.values()) {
|
|
351
|
+
if (step.sessionID === sessionID && finite(event.created) && event.created >= step.latest)
|
|
352
|
+
this.touch(step, event.created);
|
|
353
|
+
}
|
|
354
|
+
this.scheduleCleanup();
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
207
357
|
const messageID = typeof data.assistantMessageID === "string" ? data.assistantMessageID : undefined;
|
|
208
|
-
if (!
|
|
358
|
+
if (!messageID)
|
|
209
359
|
return false;
|
|
210
360
|
const key = this.key(sessionID, messageID);
|
|
211
361
|
if (event.type === "session.step.started") {
|
|
@@ -213,29 +363,65 @@ export class PerformanceMonitor {
|
|
|
213
363
|
return false;
|
|
214
364
|
if (this.active.has(key))
|
|
215
365
|
return false;
|
|
366
|
+
// A session's foreground steps are serial; missing endings must not accumulate
|
|
367
|
+
// forever just because the same session continues to run later steps.
|
|
368
|
+
for (const step of this.active.values()) {
|
|
369
|
+
if (step.sessionID !== sessionID)
|
|
370
|
+
continue;
|
|
371
|
+
if (step.started > data.started)
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
for (const step of this.active.values()) {
|
|
375
|
+
if (step.sessionID !== sessionID)
|
|
376
|
+
continue;
|
|
377
|
+
step.latest = Math.max(step.latest, data.started);
|
|
378
|
+
this.finish(step);
|
|
379
|
+
}
|
|
216
380
|
this.active.set(key, {
|
|
217
381
|
messageID, sessionID, started: data.started, bytes: 0, samples: [], events: new Set(),
|
|
382
|
+
checkedAt: this.monotonic(), revision: 0, latest: Math.max(data.started, finite(event.created) ? event.created : data.started),
|
|
218
383
|
});
|
|
384
|
+
this.scheduleCleanup();
|
|
219
385
|
return false;
|
|
220
386
|
}
|
|
221
387
|
const step = this.active.get(key);
|
|
222
388
|
if (!step)
|
|
223
389
|
return false;
|
|
390
|
+
if (event.type === "session.retry.scheduled" || (event.type.startsWith("session.tool.") && event.type !== "session.tool.input.delta")) {
|
|
391
|
+
if (!finite(event.created) || event.created < step.latest)
|
|
392
|
+
return false;
|
|
393
|
+
if (event.type === "session.retry.scheduled") {
|
|
394
|
+
delete step.finishedAt;
|
|
395
|
+
delete step.inactive;
|
|
396
|
+
delete step.reconciledAt;
|
|
397
|
+
delete step.streamed;
|
|
398
|
+
this.resetLive(step);
|
|
399
|
+
const sample = this.observed.get(key);
|
|
400
|
+
if (sample)
|
|
401
|
+
delete sample.expiresAt;
|
|
402
|
+
}
|
|
403
|
+
this.touch(step, event.created);
|
|
404
|
+
this.scheduleCleanup();
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
224
407
|
if (event.type === "session.step.streamed") {
|
|
225
408
|
if (!finite(event.created) || event.created < step.started)
|
|
226
409
|
return false;
|
|
410
|
+
if (step.streamed !== undefined && event.created <= step.streamed)
|
|
411
|
+
return false;
|
|
227
412
|
step.streamed = event.created;
|
|
413
|
+
this.touch(step, event.created);
|
|
414
|
+
this.scheduleCleanup();
|
|
228
415
|
if (step.bytes > 0)
|
|
229
416
|
this.publish(sessionID);
|
|
230
417
|
return step.bytes > 0;
|
|
231
418
|
}
|
|
232
419
|
if (event.type === "session.step.ended" || event.type === "session.step.failed") {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if (
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
this.active.delete(key);
|
|
420
|
+
if (finite(event.created) && event.created < step.latest)
|
|
421
|
+
return false;
|
|
422
|
+
if (finite(event.created))
|
|
423
|
+
step.latest = event.created;
|
|
424
|
+
this.finish(step);
|
|
239
425
|
this.scheduleCleanup();
|
|
240
426
|
return false;
|
|
241
427
|
}
|
|
@@ -244,10 +430,15 @@ export class PerformanceMonitor {
|
|
|
244
430
|
return false;
|
|
245
431
|
if (typeof data.delta !== "string" || data.delta.length === 0 || !finite(event.created) || event.created < step.started)
|
|
246
432
|
return false;
|
|
433
|
+
if (step.last !== undefined && event.created < step.last)
|
|
434
|
+
return false;
|
|
247
435
|
if (event.id && step.events.has(event.id))
|
|
248
436
|
return false;
|
|
437
|
+
if (step.finishedAt !== undefined && !step.inactive)
|
|
438
|
+
return false;
|
|
249
439
|
if (event.id)
|
|
250
440
|
step.events.add(event.id);
|
|
441
|
+
this.touch(step, event.created);
|
|
251
442
|
if (step.reconciledAt !== undefined) {
|
|
252
443
|
// A later delta means the snapshot did not represent the final stream.
|
|
253
444
|
delete step.reconciledAt;
|
|
@@ -258,41 +449,62 @@ export class PerformanceMonitor {
|
|
|
258
449
|
}
|
|
259
450
|
const sampleBytes = new TextEncoder().encode(data.delta).byteLength;
|
|
260
451
|
step.bytes += sampleBytes;
|
|
261
|
-
step.
|
|
452
|
+
if (step.last !== undefined && event.created - step.last >= LIVE_WINDOW_MS)
|
|
453
|
+
this.resetLive(step);
|
|
454
|
+
const last = step.samples.at(-1);
|
|
455
|
+
if (last && step.bucketStarted !== undefined && event.created - step.bucketStarted < LIVE_SAMPLE_MS) {
|
|
456
|
+
// Include a whole delivery burst in one batch, including the initial baseline.
|
|
457
|
+
last.time = event.created;
|
|
458
|
+
last.bytes += sampleBytes;
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
step.bucketStarted = event.created;
|
|
462
|
+
step.samples.push({ time: event.created, bytes: sampleBytes });
|
|
463
|
+
}
|
|
262
464
|
const cutoff = event.created - LIVE_WINDOW_MS;
|
|
263
465
|
while (step.samples.length > 0 && step.samples[0].time < cutoff)
|
|
264
466
|
step.samples.shift();
|
|
265
|
-
// Advance smoothing once per accepted delta, independently of summary readers.
|
|
266
467
|
const raw = estimatedLiveTps(step.samples);
|
|
267
|
-
if (raw
|
|
268
|
-
|
|
468
|
+
if (raw === undefined) {
|
|
469
|
+
delete step.liveTps;
|
|
470
|
+
delete step.liveDuration;
|
|
471
|
+
delete step.smoothedAt;
|
|
472
|
+
}
|
|
473
|
+
else if (step.smoothedAt === undefined || event.created - step.smoothedAt >= LIVE_SAMPLE_MS) {
|
|
474
|
+
step.liveTps = smoothLiveTps(step.liveTps, raw, event.created - (step.smoothedAt ?? event.created));
|
|
475
|
+
step.liveDuration = event.created - step.samples[0].time;
|
|
476
|
+
step.smoothedAt = event.created;
|
|
477
|
+
}
|
|
478
|
+
step.observedAt = this.monotonic();
|
|
269
479
|
step.last = Math.max(step.last ?? event.created, event.created);
|
|
270
480
|
if (step.first === undefined) {
|
|
271
481
|
step.first = event.created;
|
|
272
482
|
this.observed.set(key, { messageID, sessionID, started: step.started, first: event.created });
|
|
273
483
|
}
|
|
484
|
+
this.scheduleCleanup();
|
|
274
485
|
this.publish(sessionID);
|
|
275
486
|
return true;
|
|
276
487
|
}
|
|
277
488
|
reconcile(messages, sessionIDs) {
|
|
278
489
|
const sessions = new Set(sessionIDs);
|
|
279
|
-
|
|
490
|
+
if (this.disposed)
|
|
491
|
+
return;
|
|
492
|
+
const represented = new Map();
|
|
280
493
|
for (const message of messages) {
|
|
281
494
|
if (message.type === "assistant" && (message.tokens || message.time?.streamed !== undefined
|
|
282
495
|
|| message.time?.completed !== undefined))
|
|
283
|
-
represented.
|
|
496
|
+
represented.set(message.id, message);
|
|
284
497
|
}
|
|
285
|
-
for (const
|
|
498
|
+
for (const step of this.active.values()) {
|
|
286
499
|
if (sessions.has(step.sessionID) && represented.has(step.messageID)) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
sample.expiresAt ??= step.reconciledAt + this.retention;
|
|
500
|
+
// streamed/tokens describe the provider boundary, not tool settlement.
|
|
501
|
+
// Only completion or an authoritative inactive session permits expiry;
|
|
502
|
+
// a stale pre-retry completion cannot settle newer activity.
|
|
503
|
+
step.reconciledAt ??= this.monotonic();
|
|
504
|
+
const completed = represented.get(step.messageID)?.time?.completed;
|
|
505
|
+
if (finite(completed) && completed >= step.latest) {
|
|
506
|
+
step.latest = completed;
|
|
507
|
+
this.finish(step);
|
|
296
508
|
}
|
|
297
509
|
}
|
|
298
510
|
}
|
|
@@ -310,15 +522,15 @@ export class PerformanceMonitor {
|
|
|
310
522
|
// completed provider usage still replaces this estimate via reconcile().
|
|
311
523
|
let weighted = 0;
|
|
312
524
|
let durationTotal = 0;
|
|
525
|
+
const monotonic = this.monotonic();
|
|
313
526
|
for (const step of this.active.values()) {
|
|
314
527
|
if (!sessions.has(step.sessionID) || step.reconciledAt !== undefined)
|
|
315
528
|
continue;
|
|
316
|
-
if (step.liveTps === undefined || step.
|
|
529
|
+
if (step.liveTps === undefined || step.observedAt === undefined
|
|
530
|
+
|| monotonic - step.observedAt >= LIVE_WINDOW_MS)
|
|
317
531
|
continue;
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
const duration = last.time - first.time;
|
|
321
|
-
if (!finite(duration) || duration <= 0)
|
|
532
|
+
const duration = step.liveDuration;
|
|
533
|
+
if (!finite(duration) || duration < LIVE_MIN_SPAN_MS)
|
|
322
534
|
continue;
|
|
323
535
|
weighted += step.liveTps * duration;
|
|
324
536
|
durationTotal += duration;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const GENERATED_PRICE_SNAPSHOT: {
|
|
2
|
-
readonly verified: "2026-10-
|
|
2
|
+
readonly verified: "2026-10-14";
|
|
3
3
|
readonly source: "https://models.dev/api.json";
|
|
4
4
|
readonly entries: readonly [{
|
|
5
5
|
readonly providerID: "ai21";
|
|
@@ -1129,6 +1129,17 @@ export declare const GENERATED_PRICE_SNAPSHOT: {
|
|
|
1129
1129
|
};
|
|
1130
1130
|
}];
|
|
1131
1131
|
readonly released: "2026-09-02";
|
|
1132
|
+
}, {
|
|
1133
|
+
readonly providerID: "google";
|
|
1134
|
+
readonly id: "gemini-4-argon";
|
|
1135
|
+
readonly prices: readonly [{
|
|
1136
|
+
readonly input: 2;
|
|
1137
|
+
readonly output: 10;
|
|
1138
|
+
readonly cache: {
|
|
1139
|
+
readonly read: 0.1;
|
|
1140
|
+
};
|
|
1141
|
+
}];
|
|
1142
|
+
readonly released: "2026-10-13";
|
|
1132
1143
|
}, {
|
|
1133
1144
|
readonly providerID: "google";
|
|
1134
1145
|
readonly id: "gemini-flash-latest";
|
|
@@ -1250,6 +1261,17 @@ export declare const GENERATED_PRICE_SNAPSHOT: {
|
|
|
1250
1261
|
};
|
|
1251
1262
|
}];
|
|
1252
1263
|
readonly released: "2026-09-02";
|
|
1264
|
+
}, {
|
|
1265
|
+
readonly providerID: "meta";
|
|
1266
|
+
readonly id: "muse-wonder-1.0";
|
|
1267
|
+
readonly prices: readonly [{
|
|
1268
|
+
readonly input: 4;
|
|
1269
|
+
readonly output: 20;
|
|
1270
|
+
readonly cache: {
|
|
1271
|
+
readonly read: 0.2;
|
|
1272
|
+
};
|
|
1273
|
+
}];
|
|
1274
|
+
readonly released: "2026-10-12";
|
|
1253
1275
|
}, {
|
|
1254
1276
|
readonly providerID: "minimax";
|
|
1255
1277
|
readonly id: "minimax-m2";
|
package/dist/prices.generated.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const GENERATED_PRICE_SNAPSHOT = {
|
|
2
|
-
verified: "2026-10-
|
|
2
|
+
verified: "2026-10-14",
|
|
3
3
|
source: "https://models.dev/api.json",
|
|
4
4
|
entries: [
|
|
5
5
|
{ "providerID": "ai21", "id": "jamba-large", "prices": [{ "input": 2, "output": 8 }], "released": "2025-07-01" },
|
|
@@ -106,6 +106,7 @@ export const GENERATED_PRICE_SNAPSHOT = {
|
|
|
106
106
|
{ "providerID": "google", "id": "gemini-3.6-flash", "prices": [{ "input": 0.75, "output": 3.75, "cache": { "read": 0.075 } }], "released": "2026-07-21" },
|
|
107
107
|
{ "providerID": "google", "id": "gemini-3.7-flash", "prices": [{ "input": 0.75, "output": 3.75, "cache": { "read": 0.075 } }], "released": "2026-08-13" },
|
|
108
108
|
{ "providerID": "google", "id": "gemini-3.8-flash", "prices": [{ "input": 0.75, "output": 3.75, "cache": { "read": 0.075 } }], "released": "2026-09-02" },
|
|
109
|
+
{ "providerID": "google", "id": "gemini-4-argon", "prices": [{ "input": 2, "output": 10, "cache": { "read": 0.1 } }], "released": "2026-10-13" },
|
|
109
110
|
{ "providerID": "google", "id": "gemini-flash-latest", "prices": [{ "input": 0.75, "output": 3.75, "cache": { "read": 0.075 } }], "released": "2026-08-13" },
|
|
110
111
|
{ "providerID": "google", "id": "gemini-flash-lite-latest", "prices": [{ "input": 0.3, "output": 2.5, "cache": { "read": 0.03 } }], "released": "2026-07-21" },
|
|
111
112
|
{ "providerID": "inception", "id": "mercury-2", "prices": [{ "input": 0.25, "output": 0.75, "cache": { "read": 0.025 } }], "released": "2026-02-24" },
|
|
@@ -117,6 +118,7 @@ export const GENERATED_PRICE_SNAPSHOT = {
|
|
|
117
118
|
{ "providerID": "meta", "id": "muse-spark-1.2-contributor", "prices": [{ "input": 0.1, "output": 0.2, "cache": { "read": 0.002 } }], "released": "2026-08-05" },
|
|
118
119
|
{ "providerID": "meta", "id": "muse-spark-1.3", "prices": [{ "input": 1.25, "output": 4.25, "cache": { "read": 0.15 } }], "released": "2026-09-02" },
|
|
119
120
|
{ "providerID": "meta", "id": "muse-spark-1.3-contributor", "prices": [{ "input": 0.1, "output": 0.2, "cache": { "read": 0.002 } }], "released": "2026-09-02" },
|
|
121
|
+
{ "providerID": "meta", "id": "muse-wonder-1.0", "prices": [{ "input": 4, "output": 20, "cache": { "read": 0.2 } }], "released": "2026-10-12" },
|
|
120
122
|
{ "providerID": "minimax", "id": "minimax-m2", "prices": [{ "input": 0.3, "output": 1.2 }], "released": "2025-10-27" },
|
|
121
123
|
{ "providerID": "minimax", "id": "minimax-m2.1", "prices": [{ "input": 0.3, "output": 1.2, "cache": { "read": 0.03, "write": 0.375 } }], "released": "2025-12-23" },
|
|
122
124
|
{ "providerID": "minimax", "id": "minimax-m2.5", "prices": [{ "input": 0.3, "output": 1.2, "cache": { "read": 0.03, "write": 0.375 } }], "released": "2026-02-12" },
|
package/dist/pricing.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export interface OfficialPriceEntry {
|
|
|
14
14
|
/** Checked-in data: updating prices is explicit, not a network request at
|
|
15
15
|
* startup, during a refresh, or during an npm build. */
|
|
16
16
|
export declare const OFFICIAL_PRICE_SNAPSHOT: {
|
|
17
|
-
verified: "2026-10-
|
|
17
|
+
verified: "2026-10-14";
|
|
18
18
|
source: "https://models.dev/api.json";
|
|
19
19
|
entries: OfficialPriceEntry[];
|
|
20
20
|
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface RuntimeEvent {
|
|
2
|
+
type: string;
|
|
3
|
+
data: unknown;
|
|
4
|
+
created?: number;
|
|
5
|
+
}
|
|
6
|
+
type Subscribe = (listener: (event: RuntimeEvent) => void) => () => void;
|
|
7
|
+
type Listener = (sessionID?: string) => void;
|
|
8
|
+
type Tick = (now: number | undefined) => void;
|
|
9
|
+
/** Shared server-time anchor and display clock; no wall clock, execution log or periodic reads. */
|
|
10
|
+
export declare class RuntimeMonitor {
|
|
11
|
+
private readonly monotonic;
|
|
12
|
+
private anchor;
|
|
13
|
+
private latest;
|
|
14
|
+
private stopped;
|
|
15
|
+
private listeners;
|
|
16
|
+
private ticks;
|
|
17
|
+
private timer;
|
|
18
|
+
private synchronization;
|
|
19
|
+
private disposed;
|
|
20
|
+
private readonly unsubscribe;
|
|
21
|
+
constructor(subscribe?: Subscribe, monotonic?: () => number);
|
|
22
|
+
now(): number | undefined;
|
|
23
|
+
/** One optional read-only RPC supplies an anchor even when opening a quiet active tool. */
|
|
24
|
+
synchronize(read: (signal: AbortSignal) => Promise<unknown>): Promise<void>;
|
|
25
|
+
listen(listener: Listener): () => void;
|
|
26
|
+
/** Only views actually projecting a live turn keep this single 500ms ticker alive. */
|
|
27
|
+
tick(listener: Tick): () => void;
|
|
28
|
+
canEstimate(sessionID: string, messageID: string): boolean;
|
|
29
|
+
handle(event: RuntimeEvent): void;
|
|
30
|
+
dispose(): void;
|
|
31
|
+
private observe;
|
|
32
|
+
private publish;
|
|
33
|
+
}
|
|
34
|
+
export {};
|