tokenmaxx 0.0.3 → 0.0.5

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.
Files changed (2) hide show
  1. package/dist/index.js +70 -43
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -14800,6 +14800,7 @@ var init_domain = __esm(() => {
14800
14800
  TokenEventSchema = exports_external.object({
14801
14801
  accountId: exports_external.uuid().nullable(),
14802
14802
  at: exports_external.number().int().nonnegative(),
14803
+ cacheReadTokens: exports_external.number().int().nonnegative(),
14803
14804
  inputTokens: exports_external.number().int().nonnegative(),
14804
14805
  model: exports_external.string().min(1).nullable(),
14805
14806
  outputTokens: exports_external.number().int().nonnegative(),
@@ -14814,6 +14815,7 @@ var init_domain = __esm(() => {
14814
14815
  key: exports_external.string(),
14815
14816
  peakPerHour: exports_external.number().nonnegative(),
14816
14817
  topModel: exports_external.string().nullable(),
14818
+ totalCached: exports_external.number().nonnegative(),
14817
14819
  totalInput: exports_external.number().nonnegative(),
14818
14820
  totalOutput: exports_external.number().nonnegative(),
14819
14821
  totalTokens: exports_external.number().nonnegative()
@@ -15087,21 +15089,21 @@ function priceFor(model) {
15087
15089
  const key = (model ?? "").toLowerCase();
15088
15090
  return PRICES.find((entry) => key.includes(entry.match))?.price ?? DEFAULT_PRICE;
15089
15091
  }
15090
- function costUsd(model, inputTokens, outputTokens) {
15092
+ function costUsd(model, inputTokens, outputTokens, cacheReadTokens = 0) {
15091
15093
  const price = priceFor(model);
15092
- return inputTokens / 1e6 * price.inputPerMTok + outputTokens / 1e6 * price.outputPerMTok;
15094
+ return inputTokens / 1e6 * price.inputPerMTok + outputTokens / 1e6 * price.outputPerMTok + cacheReadTokens / 1e6 * price.cacheReadPerMTok;
15093
15095
  }
15094
15096
  var PRICES, DEFAULT_PRICE;
15095
15097
  var init_pricing = __esm(() => {
15096
15098
  PRICES = [
15097
- { match: "claude-opus", price: { inputPerMTok: 15, outputPerMTok: 75 } },
15098
- { match: "claude-sonnet", price: { inputPerMTok: 3, outputPerMTok: 15 } },
15099
- { match: "claude-haiku", price: { inputPerMTok: 0.8, outputPerMTok: 4 } },
15100
- { match: "claude-fable", price: { inputPerMTok: 1, outputPerMTok: 5 } },
15101
- { match: "gpt-5", price: { inputPerMTok: 1.25, outputPerMTok: 10 } },
15102
- { match: "o4", price: { inputPerMTok: 1.1, outputPerMTok: 4.4 } }
15099
+ { match: "claude-opus", price: { cacheReadPerMTok: 1.5, inputPerMTok: 15, outputPerMTok: 75 } },
15100
+ { match: "claude-sonnet", price: { cacheReadPerMTok: 0.3, inputPerMTok: 3, outputPerMTok: 15 } },
15101
+ { match: "claude-haiku", price: { cacheReadPerMTok: 0.08, inputPerMTok: 0.8, outputPerMTok: 4 } },
15102
+ { match: "claude-fable", price: { cacheReadPerMTok: 0.1, inputPerMTok: 1, outputPerMTok: 5 } },
15103
+ { match: "gpt-5", price: { cacheReadPerMTok: 0.125, inputPerMTok: 1.25, outputPerMTok: 10 } },
15104
+ { match: "o4", price: { cacheReadPerMTok: 0.28, inputPerMTok: 1.1, outputPerMTok: 4.4 } }
15103
15105
  ];
15104
- DEFAULT_PRICE = { inputPerMTok: 3, outputPerMTok: 15 };
15106
+ DEFAULT_PRICE = { cacheReadPerMTok: 0.3, inputPerMTok: 3, outputPerMTok: 15 };
15105
15107
  });
15106
15108
 
15107
15109
  // src/tui/format.ts
@@ -15141,32 +15143,24 @@ function meter(usedPercent, width = 14) {
15141
15143
  function percentLabel(usedPercent) {
15142
15144
  return usedPercent === null ? " ?%" : `${Math.round(clamp(usedPercent))}%`.padStart(4);
15143
15145
  }
15144
- function brailleLine(columns, width, height, max = 100) {
15146
+ function brailleArea(columns, width, height, max = 100) {
15145
15147
  const dotRows = height * 4;
15146
15148
  const dotCols = width * 2;
15147
15149
  const scale = max <= 0 ? 1 : max;
15148
15150
  const grid = Array.from({ length: dotCols }, () => new Array(dotRows).fill(false));
15149
- const toY = (value) => Math.max(0, Math.min(dotRows - 1, Math.round(value / scale * (dotRows - 1))));
15150
- let previousY = -1;
15151
15151
  for (let x = 0;x < dotCols; x += 1) {
15152
15152
  const value = columns[x];
15153
- if (value === null || value === undefined) {
15154
- previousY = -1;
15153
+ if (value === null || value === undefined || value <= 0) {
15155
15154
  continue;
15156
15155
  }
15157
- const y = toY(value);
15156
+ const top = Math.max(1, Math.min(dotRows, Math.round(value / scale * dotRows)));
15158
15157
  const column = grid[x];
15159
15158
  if (column === undefined) {
15160
15159
  continue;
15161
15160
  }
15162
- if (previousY >= 0) {
15163
- for (let fill = Math.min(previousY, y);fill <= Math.max(previousY, y); fill += 1) {
15164
- column[fill] = true;
15165
- }
15166
- } else {
15161
+ for (let y = 0;y < top; y += 1) {
15167
15162
  column[y] = true;
15168
15163
  }
15169
- previousY = y;
15170
15164
  }
15171
15165
  const rows = [];
15172
15166
  for (let charRow = 0;charRow < height; charRow += 1) {
@@ -15366,7 +15360,8 @@ function buildTokens(scale) {
15366
15360
  const target = Math.round(150000 * hours * 0.5 * scale);
15367
15361
  const buckets = raw.map((value) => Math.round(value / rawSum * target));
15368
15362
  const totalTokens = buckets.reduce((sum, value) => sum + value, 0);
15369
- const totalInput = Math.round(totalTokens * 0.7);
15363
+ const totalCached = Math.round(totalTokens * 0.62);
15364
+ const totalInput = Math.round(totalTokens * 0.28);
15370
15365
  const codexTokens = Math.round(totalTokens * 0.55);
15371
15366
  const claudeTokens = totalTokens - codexTokens;
15372
15367
  const codexCost = costUsd("gpt-5.6-sol", Math.round(codexTokens * 0.7), Math.round(codexTokens * 0.3));
@@ -15384,8 +15379,9 @@ function buildTokens(scale) {
15384
15379
  key: timeframe.key,
15385
15380
  peakPerHour: Math.round(peakBucket * (3600000 / bucketMs)),
15386
15381
  topModel: totalTokens === 0 ? null : "claude-opus-4-8",
15382
+ totalCached,
15387
15383
  totalInput,
15388
- totalOutput: totalTokens - totalInput,
15384
+ totalOutput: totalTokens - totalInput - totalCached,
15389
15385
  totalTokens
15390
15386
  };
15391
15387
  });
@@ -15808,28 +15804,43 @@ function timeframeBar(ctx, timeframe) {
15808
15804
  function throughputCard(ctx, tokens, timeframe, height, width) {
15809
15805
  const body = [];
15810
15806
  if (tokens === undefined || tokens.totalTokens === 0) {
15811
- for (let index = 0;index < Math.max(1, height - 1); index += 1) {
15807
+ for (let index = 0;index < Math.max(1, Math.floor(height / 2)); index += 1) {
15812
15808
  body.push(Box({ flexDirection: "row" }, Text({ content: " ", fg: rgb(ctx.theme.bg) })));
15813
15809
  }
15814
- body.push(Box({ flexDirection: "row" }, Text({ content: " no token usage yet \u2014 run ", fg: rgb(ctx.theme.dim) }), Text({ content: "codex", fg: rgb(ctx.theme.fg) }), Text({ content: " or ", fg: rgb(ctx.theme.dim) }), Text({ content: "claude", fg: rgb(ctx.theme.fg) }), Text({ content: " and it fills in", fg: rgb(ctx.theme.dim) })));
15810
+ body.push(Box({ flexDirection: "row", justifyContent: "center", width: "100%" }, Text({ content: "no token usage yet \u2014 run ", fg: rgb(ctx.theme.dim) }), Text({ content: "codex", fg: rgb(ctx.theme.fg) }), Text({ content: " or ", fg: rgb(ctx.theme.dim) }), Text({ content: "claude", fg: rgb(ctx.theme.fg) }), Text({ content: " and it fills in live", fg: rgb(ctx.theme.dim) })));
15815
15811
  } else {
15816
- const columns = throughputColumns(tokens.buckets, width * 2);
15812
+ const axisTop = `${compactNumber(tokens.peakPerHour)}/h`;
15813
+ const gutter = Math.max(6, axisTop.length);
15814
+ const chartWidth = Math.max(16, width - gutter - 1);
15815
+ const columns = throughputColumns(tokens.buckets, chartWidth * 2);
15817
15816
  const peak = Math.max(...columns, 1);
15818
- const chart = brailleLine(columns, width, height, peak);
15819
- const axisTop = `${compactNumber(tokens.peakPerHour)}/h`.padStart(6);
15817
+ const chart = brailleArea(columns, chartWidth, height, peak);
15820
15818
  chart.forEach((line, index) => {
15821
- const axis = index === 0 ? axisTop : index === chart.length - 1 ? " 0" : " ";
15822
- body.push(Box({ flexDirection: "row" }, Text({ content: `${axis} `, fg: rgb(ctx.theme.faint) }), Text({ content: line, fg: rgb(ctx.theme.accent) })));
15819
+ const label = index === 0 ? axisTop : index === chart.length - 1 ? "0" : "";
15820
+ body.push(Box({ flexDirection: "row" }, Text({ content: `${label.padStart(gutter)} `, fg: rgb(ctx.theme.faint) }), Text({ content: line, fg: rgb(ctx.theme.accent) })));
15823
15821
  });
15824
- body.push(Box({ flexDirection: "row" }, Text({ content: " ", fg: rgb(ctx.theme.bg) }), Text({
15825
- content: `${timeframe.label} ago`.padEnd(Math.max(0, width - 3)),
15822
+ body.push(Box({ flexDirection: "row" }, Text({ content: " ".repeat(gutter + 1), fg: rgb(ctx.theme.bg) }), Text({
15823
+ content: `${timeframe.label} ago`.padEnd(Math.max(0, chartWidth - 3)),
15826
15824
  fg: rgb(ctx.theme.faint)
15827
15825
  }), Text({ content: "now", fg: rgb(ctx.theme.faint) })));
15826
+ const spanHours = tokens.bucketMs * tokens.buckets.length / 3600000;
15827
+ const averagePerHour = spanHours > 0 ? tokens.totalTokens / spanHours : 0;
15828
+ const recent = tokens.buckets.slice(-2);
15829
+ const nowPerHour = Math.max(...recent, 0) * (3600000 / tokens.bucketMs);
15830
+ const cachedShare = tokens.totalTokens > 0 ? Math.round(tokens.totalCached / tokens.totalTokens * 100) : 0;
15828
15831
  body.push(Box({ flexDirection: "row", paddingLeft: 1 }, Text({ content: "\u03A3 ", fg: rgb(ctx.theme.dim) }), Text({
15829
15832
  attributes: 1,
15830
15833
  content: `${compactNumber(tokens.totalTokens)} tokens`,
15831
15834
  fg: rgb(ctx.theme.fg)
15832
- }), Text({ content: " \u2248 ", fg: rgb(ctx.theme.dim) }), Text({ attributes: 1, content: compactUsd(tokens.costUsd), fg: rgb(ctx.theme.good) }), Text({ content: " peak ", fg: rgb(ctx.theme.dim) }), Text({ content: `${compactNumber(tokens.peakPerHour)}/h`, fg: rgb(ctx.theme.accent) }), Text({
15835
+ }), Text({ content: " \u2248 ", fg: rgb(ctx.theme.dim) }), Text({ attributes: 1, content: compactUsd(tokens.costUsd), fg: rgb(ctx.theme.good) }), Text({ content: " API value", fg: rgb(ctx.theme.dim) }), Text({
15836
+ content: cachedShare > 0 ? ` ${cachedShare}% cached` : "",
15837
+ fg: rgb(ctx.theme.dim)
15838
+ })));
15839
+ body.push(Box({ flexDirection: "row", paddingLeft: 1 }, Text({ content: "now ", fg: rgb(ctx.theme.dim) }), Text({
15840
+ attributes: 1,
15841
+ content: `${compactNumber(nowPerHour)}/h`,
15842
+ fg: rgb(nowPerHour > 0 ? ctx.theme.accent : ctx.theme.faint)
15843
+ }), Text({ content: " peak ", fg: rgb(ctx.theme.dim) }), Text({ content: `${compactNumber(tokens.peakPerHour)}/h`, fg: rgb(ctx.theme.fg) }), Text({ content: " avg ", fg: rgb(ctx.theme.dim) }), Text({ content: `${compactNumber(averagePerHour)}/h`, fg: rgb(ctx.theme.fg) }), Text({
15833
15844
  content: tokens.topModel === null ? "" : ` top ${tokens.topModel}`,
15834
15845
  fg: rgb(ctx.theme.faint)
15835
15846
  })));
@@ -15859,7 +15870,7 @@ function analyticsBody(ctx, analytics, timeframe) {
15859
15870
  const cols = process.stdout.columns ?? 80;
15860
15871
  const rows = process.stdout.rows ?? 24;
15861
15872
  const width = Math.max(24, Math.min(160, cols - 12));
15862
- const height = Math.max(4, Math.min(10, rows - 16));
15873
+ const height = Math.max(4, Math.min(10, rows - 17));
15863
15874
  const tokens = analytics.tokens?.timeframes.find((entry) => entry.key === timeframe.key);
15864
15875
  return [timeframeBar(ctx, timeframe), throughputCard(ctx, tokens, timeframe, height, width)];
15865
15876
  }
@@ -17229,6 +17240,7 @@ function createUsageObserver(provider, contentType, onUsage) {
17229
17240
  let model = null;
17230
17241
  let input = 0;
17231
17242
  let output = 0;
17243
+ let cacheRead = 0;
17232
17244
  let saw = false;
17233
17245
  const maxJson = 4000000;
17234
17246
  const consume = (text) => {
@@ -17242,7 +17254,8 @@ function createUsageObserver(provider, contentType, onUsage) {
17242
17254
  if (event.type === "message_start" && event.message) {
17243
17255
  model = event.message.model ?? model;
17244
17256
  const usage2 = event.message.usage ?? {};
17245
- input += (usage2.input_tokens ?? 0) + (usage2.cache_read_input_tokens ?? 0) + (usage2.cache_creation_input_tokens ?? 0);
17257
+ input += (usage2.input_tokens ?? 0) + (usage2.cache_creation_input_tokens ?? 0);
17258
+ cacheRead += usage2.cache_read_input_tokens ?? 0;
17246
17259
  saw = true;
17247
17260
  } else if (event.type === "message_delta" && event.usage) {
17248
17261
  output = event.usage.output_tokens ?? output;
@@ -17252,7 +17265,9 @@ function createUsageObserver(provider, contentType, onUsage) {
17252
17265
  }
17253
17266
  const usage = event.response?.usage ?? event.usage;
17254
17267
  if (usage) {
17255
- input = usage.input_tokens ?? usage.prompt_tokens ?? input;
17268
+ const cached2 = usage.input_tokens_details?.cached_tokens ?? 0;
17269
+ input = Math.max(0, (usage.input_tokens ?? usage.prompt_tokens ?? input) - cached2);
17270
+ cacheRead = cached2;
17256
17271
  output = usage.output_tokens ?? usage.completion_tokens ?? output;
17257
17272
  model = event.response?.model ?? event.model ?? model;
17258
17273
  saw = true;
@@ -17263,8 +17278,9 @@ function createUsageObserver(provider, contentType, onUsage) {
17263
17278
  if (!isSse && jsonBuffer.length > 0) {
17264
17279
  consume(jsonBuffer);
17265
17280
  }
17266
- if (saw && input + output > 0) {
17281
+ if (saw && input + output + cacheRead > 0) {
17267
17282
  onUsage({
17283
+ cacheReadTokens: cacheRead,
17268
17284
  inputTokens: input,
17269
17285
  model: model && model.length > 0 ? model : null,
17270
17286
  outputTokens: output
@@ -17410,6 +17426,7 @@ function createProxyHandler(options) {
17410
17426
  if (options.record !== undefined && response.ok && forwarded.body !== null) {
17411
17427
  const observer = createUsageObserver(route.provider, response.headers.get("content-type") ?? "", (usage) => options.record?.({
17412
17428
  at: Date.now(),
17429
+ cacheReadTokens: usage.cacheReadTokens,
17413
17430
  inputTokens: usage.inputTokens,
17414
17431
  model: usage.model,
17415
17432
  outputTokens: usage.outputTokens,
@@ -17635,6 +17652,7 @@ function selectRotation(input) {
17635
17652
  function priceTokenTimeframe(aggregate) {
17636
17653
  let totalInput = 0;
17637
17654
  let totalOutput = 0;
17655
+ let totalCached = 0;
17638
17656
  let costTotal = 0;
17639
17657
  const byProvider = {
17640
17658
  anthropic: { costUsd: 0, tokens: 0 },
@@ -17642,10 +17660,11 @@ function priceTokenTimeframe(aggregate) {
17642
17660
  };
17643
17661
  let top = null;
17644
17662
  for (const entry of aggregate.byModel) {
17645
- const entryCost = costUsd(entry.model, entry.input, entry.output);
17646
- const entryTokens = entry.input + entry.output;
17663
+ const entryCost = costUsd(entry.model, entry.input, entry.output, entry.cached);
17664
+ const entryTokens = entry.input + entry.output + entry.cached;
17647
17665
  totalInput += entry.input;
17648
17666
  totalOutput += entry.output;
17667
+ totalCached += entry.cached;
17649
17668
  costTotal += entryCost;
17650
17669
  const bucket = entry.provider === "anthropic" ? byProvider.anthropic : byProvider.openai;
17651
17670
  bucket.tokens += entryTokens;
@@ -17663,9 +17682,10 @@ function priceTokenTimeframe(aggregate) {
17663
17682
  key: aggregate.key,
17664
17683
  peakPerHour: peakBucket * (3600000 / aggregate.bucketMs),
17665
17684
  topModel: top?.model ?? null,
17685
+ totalCached,
17666
17686
  totalInput,
17667
17687
  totalOutput,
17668
- totalTokens: totalInput + totalOutput
17688
+ totalTokens: totalInput + totalOutput + totalCached
17669
17689
  };
17670
17690
  }
17671
17691
  var defaultDependencies = {
@@ -18246,6 +18266,7 @@ function migrate(database) {
18246
18266
  account_id TEXT,
18247
18267
  model TEXT,
18248
18268
  input_tokens INTEGER NOT NULL DEFAULT 0,
18269
+ cache_read_tokens INTEGER NOT NULL DEFAULT 0,
18249
18270
  output_tokens INTEGER NOT NULL DEFAULT 0
18250
18271
  );
18251
18272
  CREATE INDEX IF NOT EXISTS token_events_at ON token_events(at);
@@ -18253,6 +18274,11 @@ function migrate(database) {
18253
18274
  DROP TABLE IF EXISTS runtime_sessions;
18254
18275
  `);
18255
18276
  const accountColumns = new Set(database.query("PRAGMA table_info(accounts)").all().map((column) => column.name));
18277
+ const tokenColumns = new Set(database.query("PRAGMA table_info(token_events)").all().map((column) => column.name));
18278
+ if (!tokenColumns.has("cache_read_tokens")) {
18279
+ database.exec("ALTER TABLE token_events ADD COLUMN cache_read_tokens INTEGER NOT NULL DEFAULT 0");
18280
+ database.exec("DELETE FROM token_events");
18281
+ }
18256
18282
  const requiresLabelMigration = !accountColumns.has("label");
18257
18283
  const requiresExternalAccountIdMigration = !accountColumns.has("external_account_id");
18258
18284
  const requiresExternalUserIdMigration = !accountColumns.has("external_user_id");
@@ -18440,12 +18466,12 @@ function createStateStore(databasePath) {
18440
18466
  }
18441
18467
  function recordTokenEvent(event) {
18442
18468
  const parsed = TokenEventSchema.parse(event);
18443
- database.query("INSERT INTO token_events (at, provider, account_id, model, input_tokens, output_tokens) VALUES (?, ?, ?, ?, ?, ?)").run(parsed.at, parsed.provider, parsed.accountId, parsed.model, parsed.inputTokens, parsed.outputTokens);
18469
+ database.query("INSERT INTO token_events (at, provider, account_id, model, input_tokens, output_tokens, cache_read_tokens) VALUES (?, ?, ?, ?, ?, ?, ?)").run(parsed.at, parsed.provider, parsed.accountId, parsed.model, parsed.inputTokens, parsed.outputTokens, parsed.cacheReadTokens);
18444
18470
  database.query("DELETE FROM token_events WHERE at < ?").run(parsed.at - maxTokenEventAgeMs);
18445
18471
  }
18446
18472
  function tokenAnalytics(nowMillis, timeframes) {
18447
- const bucketQuery = database.query("SELECT CAST((at - ?) / ? AS INTEGER) AS b, SUM(input_tokens + output_tokens) AS tokens FROM token_events WHERE at >= ? AND at <= ? GROUP BY b");
18448
- const modelQuery = database.query("SELECT model, provider, SUM(input_tokens) AS input, SUM(output_tokens) AS output FROM token_events WHERE at >= ? AND at <= ? GROUP BY model, provider");
18473
+ const bucketQuery = database.query("SELECT CAST((at - ?) / ? AS INTEGER) AS b, SUM(input_tokens + output_tokens + cache_read_tokens) AS tokens FROM token_events WHERE at >= ? AND at <= ? GROUP BY b");
18474
+ const modelQuery = database.query("SELECT model, provider, SUM(input_tokens) AS input, SUM(output_tokens) AS output, SUM(cache_read_tokens) AS cached FROM token_events WHERE at >= ? AND at <= ? GROUP BY model, provider");
18449
18475
  return timeframes.map(({ key, ms }) => {
18450
18476
  const start = nowMillis - ms;
18451
18477
  const bucketMs = Math.max(1, Math.round(ms / tokenBucketCount));
@@ -18455,6 +18481,7 @@ function createStateStore(databasePath) {
18455
18481
  buckets[index] = (buckets[index] ?? 0) + row.tokens;
18456
18482
  }
18457
18483
  const byModel = modelQuery.all(start, nowMillis).map((row) => ({
18484
+ cached: row.cached,
18458
18485
  input: row.input,
18459
18486
  model: row.model ?? "unknown",
18460
18487
  output: row.output,
package/package.json CHANGED
@@ -62,5 +62,5 @@
62
62
  "post-commit": "bun x @rubriclab/package post-commit"
63
63
  },
64
64
  "type": "module",
65
- "version": "0.0.3"
65
+ "version": "0.0.5"
66
66
  }