@oh-my-pi/omp-stats 18.0.3 → 18.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.
package/src/db.ts CHANGED
@@ -1,8 +1,13 @@
1
1
  import { Database } from "bun:sqlite";
2
2
  import * as fs from "node:fs/promises";
3
3
  import type { Usage } from "@oh-my-pi/pi-ai";
4
- import type { GeneratedProvider } from "@oh-my-pi/pi-catalog/models";
5
- import { calculateUncachedInputCost, getBundledModel } from "@oh-my-pi/pi-catalog/models";
4
+ import {
5
+ calculateUncachedInputCost,
6
+ calculateUsageCost,
7
+ type GeneratedProvider,
8
+ getBundledModel,
9
+ } from "@oh-my-pi/pi-catalog/models";
10
+ import type { ModelCost } from "@oh-my-pi/pi-catalog/types";
6
11
  import { getConfigRootDir, getStatsDbPath } from "@oh-my-pi/pi-utils";
7
12
  import { classifyAgentType } from "./parser";
8
13
  import type {
@@ -31,9 +36,8 @@ import type {
31
36
  UserMessageStats,
32
37
  } from "./types";
33
38
 
34
- type ModelCost = { input: number; output: number; cacheRead: number; cacheWrite: number };
35
39
  type UsageCost = Usage["cost"];
36
- type CostTokens = Pick<Usage, "input" | "output" | "cacheRead" | "cacheWrite" | "orchestration">;
40
+ type CostTokens = Pick<Usage, "input" | "output" | "cacheRead" | "cacheWrite" | "orchestration" | "cttl">;
37
41
 
38
42
  const ZERO_USAGE_COST: UsageCost = {
39
43
  input: 0,
@@ -43,6 +47,9 @@ const ZERO_USAGE_COST: UsageCost = {
43
47
  total: 0,
44
48
  };
45
49
 
50
+ const UNPRICED_XAI_OAUTH_SQL =
51
+ "CASE WHEN provider = 'xai-oauth' AND total_tokens > 0 AND cost_total = 0 THEN 1 ELSE 0 END";
52
+
46
53
  interface CostBackfillRow {
47
54
  id: number;
48
55
  provider: string;
@@ -71,6 +78,7 @@ interface AggregatedStatsRow {
71
78
  total_cache_write_tokens: number | null;
72
79
  total_premium_requests: number | null;
73
80
  total_cost: number | null;
81
+ unpriced_requests: number | null;
74
82
  total_cached_prompt_cost: number | null;
75
83
  total_no_cache_input_cost: number | null;
76
84
  avg_duration: number | null;
@@ -89,6 +97,19 @@ interface FolderStatsRow extends AggregatedStatsRow {
89
97
  folder: string;
90
98
  }
91
99
 
100
+ interface CostTimeSeriesRow {
101
+ bucket: number;
102
+ model: string;
103
+ provider: string;
104
+ cost: number | null;
105
+ unpriced_requests: number | null;
106
+ cost_input: number | null;
107
+ cost_output: number | null;
108
+ cost_cache_read: number | null;
109
+ cost_cache_write: number | null;
110
+ requests: number;
111
+ }
112
+
92
113
  let db: Database | null = null;
93
114
 
94
115
  const BACKFILL_COMPLETE = "complete";
@@ -99,6 +120,12 @@ const PRIORITY_PREMIUM_REQUESTS_BACKFILL_KEY = "premium_requests_priority_v1";
99
120
  const AGENT_TYPE_BACKFILL_KEY = "agent_type_v1";
100
121
  const FORK_DEDUPE_KEY = "fork_dedupe_v1";
101
122
  const TOOL_CALLS_BACKFILL_KEY = "tool_calls_v1";
123
+ // Older ingests dropped `Usage.orchestration` (never a stored column) when
124
+ // pricing, so subscription models billed on orchestration tokens — multi-agent
125
+ // Grok most notably — were priced from conversation buckets alone and could not
126
+ // reach the inclusive 200K tier. A one-time full re-parse repairs them through
127
+ // the cost-refreshing UPSERT in `insertMessageStats`.
128
+ const COST_REINGEST_BACKFILL_KEY = "messages_cost_reingest_v1";
102
129
  function shouldResetBackfill(value: string | undefined): boolean {
103
130
  return value !== BACKFILL_COMPLETE && value !== BACKFILL_PENDING;
104
131
  }
@@ -301,6 +328,7 @@ export async function initDb(): Promise<Database> {
301
328
  }
302
329
  backfillUserMessages(db);
303
330
  backfillToolCalls(db);
331
+ backfillReingestCosts(db);
304
332
  repairUserMessageLinks(db);
305
333
  backfillPriorityPremiumRequests(db);
306
334
  backfillAgentType(db);
@@ -325,10 +353,11 @@ function getCatalogCost(provider: string, modelId: string): ModelCost | null {
325
353
  return primaryCost;
326
354
  }
327
355
 
328
- if (provider === "openai-codex") {
329
- const openAICost = getBundledModelCost("openai", modelId);
330
- if (openAICost && hasBillableCost(openAICost)) {
331
- return openAICost;
356
+ const fallbackProvider = provider === "openai-codex" ? "openai" : provider === "xai-oauth" ? "xai" : null;
357
+ if (fallbackProvider) {
358
+ const fallbackCost = getBundledModelCost(fallbackProvider, modelId);
359
+ if (fallbackCost && hasBillableCost(fallbackCost)) {
360
+ return fallbackCost;
332
361
  }
333
362
  }
334
363
 
@@ -339,18 +368,20 @@ function calculateCatalogCost(provider: string, modelId: string, tokens: CostTok
339
368
  const cost = getCatalogCost(provider, modelId);
340
369
  if (!cost) return null;
341
370
 
342
- const input = (cost.input / 1_000_000) * tokens.input;
343
- const output = (cost.output / 1_000_000) * tokens.output;
344
- const cacheRead = (cost.cacheRead / 1_000_000) * tokens.cacheRead;
345
- const cacheWrite = (cost.cacheWrite / 1_000_000) * tokens.cacheWrite;
346
-
347
- return {
348
- input,
349
- output,
350
- cacheRead,
351
- cacheWrite,
352
- total: input + output + cacheRead + cacheWrite,
371
+ const orchestration = tokens.orchestration;
372
+ const usage: Usage = {
373
+ ...tokens,
374
+ totalTokens:
375
+ tokens.input +
376
+ tokens.output +
377
+ tokens.cacheRead +
378
+ tokens.cacheWrite +
379
+ (orchestration?.input ?? 0) +
380
+ (orchestration?.output ?? 0) +
381
+ (orchestration?.cacheRead ?? 0),
382
+ cost: { ...ZERO_USAGE_COST },
353
383
  };
384
+ return calculateUsageCost(cost, usage);
354
385
  }
355
386
 
356
387
  function normalizeUsageCost(cost: UsageCost): UsageCost {
@@ -486,8 +517,9 @@ export function setFileOffset(sessionFile: string, offset: number, lastModified:
486
517
  * aggregate. The `WHERE NOT EXISTS` clause skips inserts whose
487
518
  * `(entry_id, timestamp)` already exists under a different `session_file` —
488
519
  * first-write-wins across the lineage. Same-file re-syncs still hit the
489
- * `ON CONFLICT(session_file, entry_id)` upsert below so historical
490
- * `premium_requests` fix-ups continue to work.
520
+ * `ON CONFLICT(session_file, entry_id)` upsert below, which re-derives the
521
+ * stored cost (orchestration-aware) and keeps `premium_requests` monotonic, so
522
+ * a forced re-parse repairs historical `premium_requests` and cost fix-ups.
491
523
  */
492
524
  export function insertMessageStats(stats: MessageStats[]): number {
493
525
  if (!db || stats.length === 0) return 0;
@@ -505,8 +537,13 @@ export function insertMessageStats(stats: MessageStats[]): number {
505
537
  WHERE entry_id = ? AND timestamp = ? AND session_file <> ?
506
538
  )
507
539
  ON CONFLICT(session_file, entry_id) DO UPDATE SET
508
- premium_requests = excluded.premium_requests
509
- WHERE messages.premium_requests < excluded.premium_requests
540
+ premium_requests = MAX(messages.premium_requests, excluded.premium_requests),
541
+ cost_input = excluded.cost_input,
542
+ cost_output = excluded.cost_output,
543
+ cost_cache_read = excluded.cost_cache_read,
544
+ cost_cache_write = excluded.cost_cache_write,
545
+ cost_total = excluded.cost_total,
546
+ cost_no_cache_input = excluded.cost_no_cache_input
510
547
  `);
511
548
 
512
549
  let inserted = 0;
@@ -570,6 +607,7 @@ function buildAggregatedStats(rows: AggregatedStatsRow[]): AggregatedStats {
570
607
  cacheRate: 0,
571
608
  cacheSavings: 0,
572
609
  totalCost: 0,
610
+ unpricedRequests: 0,
573
611
  totalPremiumRequests: 0,
574
612
  avgDuration: null,
575
613
  avgTtft: null,
@@ -604,6 +642,7 @@ function buildAggregatedStats(rows: AggregatedStatsRow[]): AggregatedStats {
604
642
  : 0,
605
643
  cacheSavings: noCacheInputCost > 0 ? (noCacheInputCost - cachedPromptCost) / noCacheInputCost : 0,
606
644
  totalCost: row.total_cost || 0,
645
+ unpricedRequests: row.unpriced_requests || 0,
607
646
  totalPremiumRequests,
608
647
  avgDuration: row.avg_duration,
609
648
  avgTtft: row.avg_ttft,
@@ -630,6 +669,7 @@ export function getOverallStats(cutoff?: number): AggregatedStats {
630
669
  SUM(cache_write_tokens) as total_cache_write_tokens,
631
670
  SUM(premium_requests) as total_premium_requests,
632
671
  SUM(cost_total) as total_cost,
672
+ SUM(${UNPRICED_XAI_OAUTH_SQL}) as unpriced_requests,
633
673
  SUM(CASE WHEN cost_no_cache_input > 0
634
674
  THEN cost_input + cost_cache_read + cost_cache_write
635
675
  ELSE 0 END) as total_cached_prompt_cost,
@@ -665,6 +705,7 @@ export function getStatsByModel(cutoff?: number): ModelStats[] {
665
705
  SUM(cache_write_tokens) as total_cache_write_tokens,
666
706
  SUM(premium_requests) as total_premium_requests,
667
707
  SUM(cost_total) as total_cost,
708
+ SUM(${UNPRICED_XAI_OAUTH_SQL}) as unpriced_requests,
668
709
  SUM(CASE WHEN cost_no_cache_input > 0
669
710
  THEN cost_input + cost_cache_read + cost_cache_write
670
711
  ELSE 0 END) as total_cached_prompt_cost,
@@ -706,6 +747,7 @@ export function getStatsByFolder(cutoff?: number): FolderStats[] {
706
747
  SUM(cache_write_tokens) as total_cache_write_tokens,
707
748
  SUM(premium_requests) as total_premium_requests,
708
749
  SUM(cost_total) as total_cost,
750
+ SUM(${UNPRICED_XAI_OAUTH_SQL}) as unpriced_requests,
709
751
  SUM(CASE WHEN cost_no_cache_input > 0
710
752
  THEN cost_input + cost_cache_read + cost_cache_write
711
753
  ELSE 0 END) as total_cached_prompt_cost,
@@ -854,6 +896,7 @@ export function getStatsByProvider(cutoff?: number | null): ProviderAggregate[]
854
896
  SUM(cache_write_tokens) as total_cache_write_tokens,
855
897
  SUM(input_tokens + output_tokens + cache_read_tokens + cache_write_tokens) as total_tokens,
856
898
  SUM(cost_total) as total_cost,
899
+ SUM(${UNPRICED_XAI_OAUTH_SQL}) as unpriced_requests,
857
900
  SUM(premium_requests) as total_premium_requests,
858
901
  AVG(CASE WHEN duration > 0 THEN output_tokens * 1000.0 / duration ELSE NULL END) as avg_tokens_per_second
859
902
  FROM messages
@@ -873,6 +916,7 @@ export function getStatsByProvider(cutoff?: number | null): ProviderAggregate[]
873
916
  total_cache_write_tokens: number | null;
874
917
  total_tokens: number | null;
875
918
  total_cost: number | null;
919
+ unpriced_requests: number | null;
876
920
  total_premium_requests: number | null;
877
921
  avg_tokens_per_second: number | null;
878
922
  }>;
@@ -887,6 +931,7 @@ export function getStatsByProvider(cutoff?: number | null): ProviderAggregate[]
887
931
  totalCacheWriteTokens: row.total_cache_write_tokens ?? 0,
888
932
  totalTokens: row.total_tokens ?? 0,
889
933
  totalCost: row.total_cost ?? 0,
934
+ unpricedRequests: row.unpriced_requests ?? 0,
890
935
  totalPremiumRequests: row.total_premium_requests ?? 0,
891
936
  avgTokensPerSecond: row.avg_tokens_per_second,
892
937
  }));
@@ -949,6 +994,7 @@ export function getProviderTimeSeries(
949
994
  provider,
950
995
  SUM(input_tokens + output_tokens + cache_read_tokens + cache_write_tokens) as total_tokens,
951
996
  SUM(cost_total) as cost,
997
+ SUM(${UNPRICED_XAI_OAUTH_SQL}) as unpriced_requests,
952
998
  COUNT(*) as requests
953
999
  FROM messages
954
1000
  ${hasCutoff ? "WHERE timestamp >= ?" : ""}
@@ -962,6 +1008,7 @@ export function getProviderTimeSeries(
962
1008
  provider: string;
963
1009
  total_tokens: number | null;
964
1010
  cost: number | null;
1011
+ unpriced_requests: number | null;
965
1012
  requests: number;
966
1013
  }>;
967
1014
  return rows.map(row => ({
@@ -969,6 +1016,7 @@ export function getProviderTimeSeries(
969
1016
  provider: row.provider,
970
1017
  totalTokens: row.total_tokens ?? 0,
971
1018
  cost: row.cost ?? 0,
1019
+ unpricedRequests: row.unpriced_requests ?? 0,
972
1020
  requests: row.requests,
973
1021
  }));
974
1022
  }
@@ -1118,6 +1166,7 @@ export function getCostTimeSeries(days = 90, cutoff?: number | null): CostTimeSe
1118
1166
  model,
1119
1167
  provider,
1120
1168
  SUM(cost_total) as cost,
1169
+ SUM(${UNPRICED_XAI_OAUTH_SQL}) as unpriced_requests,
1121
1170
  SUM(cost_input) as cost_input,
1122
1171
  SUM(cost_output) as cost_output,
1123
1172
  SUM(cost_cache_read) as cost_cache_read,
@@ -1129,16 +1178,17 @@ export function getCostTimeSeries(days = 90, cutoff?: number | null): CostTimeSe
1129
1178
  ORDER BY bucket ASC
1130
1179
  `);
1131
1180
 
1132
- const rows = hasCutoff ? (stmt.all(seriesCutoff) as any[]) : (stmt.all() as any[]);
1181
+ const rows = (hasCutoff ? stmt.all(seriesCutoff) : stmt.all()) as CostTimeSeriesRow[];
1133
1182
  return rows.map(row => ({
1134
1183
  timestamp: row.bucket,
1135
1184
  model: row.model,
1136
1185
  provider: row.provider,
1137
- cost: row.cost,
1138
- costInput: row.cost_input,
1139
- costOutput: row.cost_output,
1140
- costCacheRead: row.cost_cache_read,
1141
- costCacheWrite: row.cost_cache_write,
1186
+ cost: row.cost ?? 0,
1187
+ unpricedRequests: row.unpriced_requests ?? 0,
1188
+ costInput: row.cost_input ?? 0,
1189
+ costOutput: row.cost_output ?? 0,
1190
+ costCacheRead: row.cost_cache_read ?? 0,
1191
+ costCacheWrite: row.cost_cache_write ?? 0,
1142
1192
  requests: row.requests,
1143
1193
  }));
1144
1194
  }
@@ -1216,6 +1266,29 @@ function backfillToolCalls(database: Database): void {
1216
1266
  .run(TOOL_CALLS_BACKFILL_KEY, BACKFILL_PENDING);
1217
1267
  }
1218
1268
 
1269
+ /**
1270
+ * One-shot `file_offsets` wipe so the next sync re-parses every session and
1271
+ * re-prices `messages` from source. Pre-fix ingests priced subscription rows
1272
+ * from the four stored token buckets only, dropping `Usage.orchestration`
1273
+ * (never a stored column), so multi-agent Grok and any other orchestration-
1274
+ * billed model were understated and could not reach the inclusive 200K tier.
1275
+ * The re-parse reruns the orchestration-aware pricing in `resolveStoredCost`
1276
+ * and the cost-refreshing UPSERT in {@link insertMessageStats} writes it back.
1277
+ * `messages`/`user_messages`/`tool_calls` re-inserts are idempotent, so the
1278
+ * offset reset is safe. Same sentinel protocol as {@link backfillToolCalls}.
1279
+ */
1280
+ function backfillReingestCosts(database: Database): void {
1281
+ const row = database.prepare("SELECT value FROM meta WHERE key = ?").get(COST_REINGEST_BACKFILL_KEY) as
1282
+ | { value: string }
1283
+ | undefined;
1284
+ if (!shouldResetBackfill(row?.value)) return;
1285
+
1286
+ database.run("DELETE FROM file_offsets");
1287
+ database
1288
+ .prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)")
1289
+ .run(COST_REINGEST_BACKFILL_KEY, BACKFILL_PENDING);
1290
+ }
1291
+
1219
1292
  /**
1220
1293
  * Reclassify pre-existing `messages` rows by agent type once, after the
1221
1294
  * `agent_type` column is added to an older database (every prior row defaulted
@@ -1341,6 +1414,7 @@ export function markSessionBackfillsComplete(): void {
1341
1414
  TOOL_CALLS_BACKFILL_KEY,
1342
1415
  USER_MESSAGE_LINKS_REPAIR_KEY,
1343
1416
  PRIORITY_PREMIUM_REQUESTS_BACKFILL_KEY,
1417
+ COST_REINGEST_BACKFILL_KEY,
1344
1418
  ]) {
1345
1419
  markComplete.run(key, BACKFILL_COMPLETE);
1346
1420
  }
@@ -1700,6 +1774,8 @@ const TOOL_AGGREGATE_COLUMNS = `
1700
1774
  SUM(COALESCE(m.total_tokens, 0) * 1.0 / t.calls_in_turn) as total_tokens_share,
1701
1775
  SUM(COALESCE(m.output_tokens, 0) * 1.0 / t.calls_in_turn) as output_tokens_share,
1702
1776
  SUM(COALESCE(m.cost_total, 0) / t.calls_in_turn) as cost_share,
1777
+ SUM(CASE WHEN t.provider = 'xai-oauth' AND COALESCE(m.total_tokens, 0) > 0 AND COALESCE(m.cost_total, 0) = 0
1778
+ THEN 1.0 / t.calls_in_turn ELSE 0 END) as unpriced_requests_share,
1703
1779
  MAX(t.timestamp) as last_used
1704
1780
  `;
1705
1781
 
@@ -1714,6 +1790,7 @@ interface ToolAggregateRow {
1714
1790
  total_tokens_share: number | null;
1715
1791
  output_tokens_share: number | null;
1716
1792
  cost_share: number | null;
1793
+ unpriced_requests_share: number | null;
1717
1794
  last_used: number;
1718
1795
  }
1719
1796
 
@@ -1727,6 +1804,7 @@ function rowToToolUsage(row: ToolAggregateRow): ToolUsageStats {
1727
1804
  totalTokensShare: row.total_tokens_share ?? 0,
1728
1805
  outputTokensShare: row.output_tokens_share ?? 0,
1729
1806
  costShare: row.cost_share ?? 0,
1807
+ unpricedRequestsShare: row.unpriced_requests_share ?? 0,
1730
1808
  lastUsed: row.last_used,
1731
1809
  };
1732
1810
  }
package/src/index.ts CHANGED
@@ -39,10 +39,9 @@ export type {
39
39
  ToolUsageStats,
40
40
  } from "./types";
41
41
 
42
- /**
43
- * Format cost in dollars.
44
- */
45
- function formatCost(n: number): string {
42
+ /** Format an API-equivalent estimate in dollars, or N/A for unpriced usage. */
43
+ function formatCost(n: number, unpricedRequests = 0): string {
44
+ if (n === 0 && unpricedRequests > 0) return "N/A";
46
45
  if (n < 0.01) return `$${n.toFixed(4)}`;
47
46
  if (n < 1) return `$${n.toFixed(3)}`;
48
47
  return `$${n.toFixed(2)}`;
@@ -69,7 +68,7 @@ async function printStats(): Promise<void> {
69
68
  console.log(` Output Tokens: ${formatNumber(overall.totalOutputTokens)}`);
70
69
  console.log(` Cache Rate: ${formatPercent(overall.cacheRate)}`);
71
70
  console.log(` Cache Savings: ${formatPercent(overall.cacheSavings)}`);
72
- console.log(` Total Cost: ${formatCost(overall.totalCost)}`);
71
+ console.log(` API-equivalent estimate: ${formatCost(overall.totalCost, overall.unpricedRequests)}`);
73
72
  console.log(` Premium Requests: ${formatNumber(normalizePremiumRequests(overall.totalPremiumRequests ?? 0))}`);
74
73
  console.log(` Avg Duration: ${overall.avgDuration !== null ? formatDuration(overall.avgDuration) : "-"}`);
75
74
  console.log(` Avg TTFT: ${overall.avgTtft !== null ? formatDuration(overall.avgTtft) : "-"}`);
@@ -78,18 +77,20 @@ async function printStats(): Promise<void> {
78
77
  }
79
78
 
80
79
  if (byModel.length > 0) {
81
- console.log("\nBy Model:");
80
+ console.log("\nBy Model (API-equivalent estimates):");
82
81
  for (const m of byModel.slice(0, 10)) {
83
82
  console.log(
84
- ` ${m.model}: ${formatNumber(m.totalRequests)} reqs, ${formatCost(m.totalCost)}, ${formatPercent(m.cacheRate)} cache rate, ${formatPercent(m.cacheSavings)} cache savings`,
83
+ ` ${m.model}: ${formatNumber(m.totalRequests)} reqs, ${formatCost(m.totalCost, m.unpricedRequests)}, ${formatPercent(m.cacheRate)} cache rate, ${formatPercent(m.cacheSavings)} cache savings`,
85
84
  );
86
85
  }
87
86
  }
88
87
 
89
88
  if (byFolder.length > 0) {
90
- console.log("\nBy Folder:");
89
+ console.log("\nBy Folder (API-equivalent estimates):");
91
90
  for (const f of byFolder.slice(0, 10)) {
92
- console.log(` ${f.folder}: ${formatNumber(f.totalRequests)} reqs, ${formatCost(f.totalCost)}`);
91
+ console.log(
92
+ ` ${f.folder}: ${formatNumber(f.totalRequests)} reqs, ${formatCost(f.totalCost, f.unpricedRequests)}`,
93
+ );
93
94
  }
94
95
  }
95
96
 
@@ -34,6 +34,8 @@ export interface AggregatedStats {
34
34
  cacheSavings: number;
35
35
  /** Total cost */
36
36
  totalCost: number;
37
+ /** Requests with token usage but no public-equivalent subscription price. */
38
+ unpricedRequests: number;
37
39
  /** Total premium requests */
38
40
  totalPremiumRequests: number;
39
41
  /** Average duration in ms */
@@ -122,6 +124,8 @@ export interface CostTimeSeriesPoint {
122
124
  provider: string;
123
125
  /** Total cost for this bucket */
124
126
  cost: number;
127
+ /** Requests excluded because no public-equivalent subscription price exists. */
128
+ unpricedRequests: number;
125
129
  /** Cost breakdown */
126
130
  costInput: number;
127
131
  costOutput: number;
@@ -302,6 +306,8 @@ export interface ToolUsageStats {
302
306
  outputTokensShare: number;
303
307
  /** Cost (USD) of invoking turns, attributed per call share. */
304
308
  costShare: number;
309
+ /** Share of unpriced subscription requests attributed to this tool. */
310
+ unpricedRequestsShare: number;
305
311
  /** Unix ms of the most recent call in range. */
306
312
  lastUsed: number;
307
313
  }
@@ -343,6 +349,8 @@ export interface ProviderAggregate {
343
349
  /** Uncached input + cache reads + cache writes + output. */
344
350
  totalTokens: number;
345
351
  totalCost: number;
352
+ /** Requests excluded because no public-equivalent subscription price exists. */
353
+ unpricedRequests: number;
346
354
  totalPremiumRequests: number;
347
355
  avgTokensPerSecond: number | null;
348
356
  }
@@ -366,6 +374,8 @@ export interface ProviderTimeSeriesPoint {
366
374
  provider: string;
367
375
  totalTokens: number;
368
376
  cost: number;
377
+ /** Requests excluded because no public-equivalent subscription price exists. */
378
+ unpricedRequests: number;
369
379
  requests: number;
370
380
  }
371
381