@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.
@@ -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
- /** Raw live throughput from one step's recent observable deltas. */
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 <= 0)
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
- bytes += sample.bytes;
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
- export function smoothLiveTps(previous, raw) {
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
- return previous * (1 - LIVE_EWMA_ALPHA) + raw * LIVE_EWMA_ALPHA;
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
- constructor(subscribe, retention = 60_000, now = Date.now) {
119
- this.retention = retention;
120
- this.now = now;
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 now = this.now();
260
+ const monotonic = this.monotonic();
155
261
  const changed = new Set();
156
262
  for (const [key, step] of this.active) {
157
- if ((step.finishedAt ?? step.reconciledAt) !== undefined
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 <= now) {
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
- if (this.disposed || !Number.isFinite(this.retention) || this.retention < 0)
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
- const settled = step.finishedAt ?? step.reconciledAt;
178
- if (settled !== undefined)
179
- next = Math.min(next, settled + this.retention);
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, next - this.now()));
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 (!sessionID || !messageID)
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
- step.finishedAt = this.now();
234
- const sample = this.observed.get(key);
235
- if (sample)
236
- sample.expiresAt = step.finishedAt + this.retention;
237
- if (step.reconciledAt !== undefined)
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.samples.push({ time: event.created, bytes: sampleBytes });
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 !== undefined)
268
- step.liveTps = smoothLiveTps(step.liveTps, raw);
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
- const represented = new Set();
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.add(message.id);
496
+ represented.set(message.id, message);
284
497
  }
285
- for (const [key, step] of this.active) {
498
+ for (const step of this.active.values()) {
286
499
  if (sessions.has(step.sessionID) && represented.has(step.messageID)) {
287
- if (step.finishedAt !== undefined)
288
- this.active.delete(key);
289
- else {
290
- // A snapshot can arrive before step.ended. Retain the step to attach
291
- // the real expiry later, and set a fallback if that event is lost.
292
- step.reconciledAt ??= this.now();
293
- const sample = this.observed.get(key);
294
- if (sample)
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.samples.length < 2)
529
+ if (step.liveTps === undefined || step.observedAt === undefined
530
+ || monotonic - step.observedAt >= LIVE_WINDOW_MS)
317
531
  continue;
318
- const first = step.samples[0];
319
- const last = step.samples[step.samples.length - 1];
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-08";
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";
@@ -1,5 +1,5 @@
1
1
  export const GENERATED_PRICE_SNAPSHOT = {
2
- verified: "2026-10-08",
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-08";
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 {};