@diogonzafe/tokenwatch 0.7.0 → 0.9.0

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,4 +1,4 @@
1
- import { I as IExporter, U as UsageEntry } from './index-D9xq0RNg.cjs';
1
+ import { I as IExporter, U as UsageEntry } from './index-fD5QLTWg.cjs';
2
2
 
3
3
  interface OTelAttributes {
4
4
  [key: string]: string | number | boolean | undefined;
@@ -1,4 +1,4 @@
1
- import { I as IExporter, U as UsageEntry } from './index-D9xq0RNg.js';
1
+ import { I as IExporter, U as UsageEntry } from './index-fD5QLTWg.js';
2
2
 
3
3
  interface OTelAttributes {
4
4
  [key: string]: string | number | boolean | undefined;
@@ -41,6 +41,9 @@ interface IExporter {
41
41
  interface TrackerConfig {
42
42
  /** 'memory' (default), 'sqlite', or a custom IStorage instance (e.g. PostgresStorage, MySQLStorage, MongoStorage) */
43
43
  storage?: 'memory' | 'sqlite' | IStorage;
44
+ /** Tag all entries recorded by this tracker with an application identifier.
45
+ * Useful when multiple apps share a single database — each tracker stamps its own appId. */
46
+ appId?: string;
44
47
  /** USD threshold — fires webhookUrl when totalCostUSD exceeds this */
45
48
  alertThreshold?: number;
46
49
  /** Discord / Slack / generic webhook URL */
@@ -62,6 +65,10 @@ interface TrackerConfig {
62
65
  anomalyDetection?: AnomalyDetectionConfig;
63
66
  /** Custom exporter called after every tracked call (e.g. OTelExporter) */
64
67
  exporter?: IExporter;
68
+ /** TokenWatch Cloud API key — mirrors every tracked call to the cloud dashboard (fire-and-forget) */
69
+ cloudApiKey?: string;
70
+ /** Override the cloud ingest endpoint (default: https://api.tokenwatch.dev/v1/ingest) */
71
+ cloudEndpoint?: string;
65
72
  }
66
73
  interface UsageEntry {
67
74
  model: string;
@@ -81,6 +88,8 @@ interface UsageEntry {
81
88
  userId?: string;
82
89
  /** Product feature that triggered this call (set via __feature in provider params) */
83
90
  feature?: string;
91
+ /** Application identifier — set once in TrackerConfig.appId and stamped on every entry */
92
+ appId?: string;
84
93
  timestamp: string;
85
94
  }
86
95
  interface ModelStats {
@@ -105,6 +114,10 @@ interface FeatureStats {
105
114
  costUSD: number;
106
115
  calls: number;
107
116
  }
117
+ interface AppStats {
118
+ costUSD: number;
119
+ calls: number;
120
+ }
108
121
  interface ReportOptions {
109
122
  /** ISO string or Date — only include entries at or after this time */
110
123
  since?: string | Date;
@@ -123,6 +136,7 @@ interface Report {
123
136
  bySession: Record<string, SessionStats>;
124
137
  byUser: Record<string, UserStats>;
125
138
  byFeature: Record<string, FeatureStats>;
139
+ byApp: Record<string, AppStats>;
126
140
  period: {
127
141
  from: string;
128
142
  to: string;
@@ -176,6 +190,8 @@ interface TrackingMeta {
176
190
  __userId?: string;
177
191
  /** Tag this call with a product feature name — appears in report.byFeature */
178
192
  __feature?: string;
193
+ /** Override the tracker-level appId for this specific call */
194
+ __appId?: string;
179
195
  }
180
196
 
181
197
  export type { AnomalyDetectionConfig as A, BudgetConfig as B, CostForecast as C, FeatureStats as F, IExporter as I, LazyTracker as L, ModelPrice as M, PriceMap as P, Report as R, SessionStats as S, TrackerConfig as T, UsageEntry as U, Tracker as a, TrackingMeta as b, ForecastOptions as c, IStorage as d, ModelStats as e, PricesFile as f, ReportOptions as g, UserStats as h };
@@ -41,6 +41,9 @@ interface IExporter {
41
41
  interface TrackerConfig {
42
42
  /** 'memory' (default), 'sqlite', or a custom IStorage instance (e.g. PostgresStorage, MySQLStorage, MongoStorage) */
43
43
  storage?: 'memory' | 'sqlite' | IStorage;
44
+ /** Tag all entries recorded by this tracker with an application identifier.
45
+ * Useful when multiple apps share a single database — each tracker stamps its own appId. */
46
+ appId?: string;
44
47
  /** USD threshold — fires webhookUrl when totalCostUSD exceeds this */
45
48
  alertThreshold?: number;
46
49
  /** Discord / Slack / generic webhook URL */
@@ -62,6 +65,10 @@ interface TrackerConfig {
62
65
  anomalyDetection?: AnomalyDetectionConfig;
63
66
  /** Custom exporter called after every tracked call (e.g. OTelExporter) */
64
67
  exporter?: IExporter;
68
+ /** TokenWatch Cloud API key — mirrors every tracked call to the cloud dashboard (fire-and-forget) */
69
+ cloudApiKey?: string;
70
+ /** Override the cloud ingest endpoint (default: https://api.tokenwatch.dev/v1/ingest) */
71
+ cloudEndpoint?: string;
65
72
  }
66
73
  interface UsageEntry {
67
74
  model: string;
@@ -81,6 +88,8 @@ interface UsageEntry {
81
88
  userId?: string;
82
89
  /** Product feature that triggered this call (set via __feature in provider params) */
83
90
  feature?: string;
91
+ /** Application identifier — set once in TrackerConfig.appId and stamped on every entry */
92
+ appId?: string;
84
93
  timestamp: string;
85
94
  }
86
95
  interface ModelStats {
@@ -105,6 +114,10 @@ interface FeatureStats {
105
114
  costUSD: number;
106
115
  calls: number;
107
116
  }
117
+ interface AppStats {
118
+ costUSD: number;
119
+ calls: number;
120
+ }
108
121
  interface ReportOptions {
109
122
  /** ISO string or Date — only include entries at or after this time */
110
123
  since?: string | Date;
@@ -123,6 +136,7 @@ interface Report {
123
136
  bySession: Record<string, SessionStats>;
124
137
  byUser: Record<string, UserStats>;
125
138
  byFeature: Record<string, FeatureStats>;
139
+ byApp: Record<string, AppStats>;
126
140
  period: {
127
141
  from: string;
128
142
  to: string;
@@ -176,6 +190,8 @@ interface TrackingMeta {
176
190
  __userId?: string;
177
191
  /** Tag this call with a product feature name — appears in report.byFeature */
178
192
  __feature?: string;
193
+ /** Override the tracker-level appId for this specific call */
194
+ __appId?: string;
179
195
  }
180
196
 
181
197
  export type { AnomalyDetectionConfig as A, BudgetConfig as B, CostForecast as C, FeatureStats as F, IExporter as I, LazyTracker as L, ModelPrice as M, PriceMap as P, Report as R, SessionStats as S, TrackerConfig as T, UsageEntry as U, Tracker as a, TrackingMeta as b, ForecastOptions as c, IStorage as d, ModelStats as e, PricesFile as f, ReportOptions as g, UserStats as h };
package/dist/index.cjs CHANGED
@@ -64,6 +64,40 @@ function calculateCost(inputTokens, outputTokens, price, cachedTokens = 0, cache
64
64
  return regularInputCost + cachedReadCost + cacheCreationCost + outputCost;
65
65
  }
66
66
 
67
+ // src/exporters/cloud.ts
68
+ var DEFAULT_ENDPOINT = "https://api.tokenwatch.dev/v1/ingest";
69
+ var CloudExporter = class {
70
+ constructor(apiKey, endpoint) {
71
+ this.apiKey = apiKey;
72
+ this.endpoint = endpoint ?? DEFAULT_ENDPOINT;
73
+ }
74
+ apiKey;
75
+ endpoint;
76
+ export(entry) {
77
+ fetch(this.endpoint, {
78
+ method: "POST",
79
+ headers: {
80
+ "Content-Type": "application/json",
81
+ Authorization: `Bearer ${this.apiKey}`
82
+ },
83
+ body: JSON.stringify({
84
+ model: entry.model,
85
+ inputTokens: entry.inputTokens,
86
+ outputTokens: entry.outputTokens,
87
+ reasoningTokens: entry.reasoningTokens ?? 0,
88
+ cachedTokens: entry.cachedTokens ?? 0,
89
+ cacheCreationTokens: entry.cacheCreationTokens ?? 0,
90
+ costUSD: entry.costUSD,
91
+ sessionId: entry.sessionId,
92
+ userId: entry.userId,
93
+ feature: entry.feature,
94
+ timestamp: entry.timestamp
95
+ })
96
+ }).catch(() => {
97
+ });
98
+ }
99
+ };
100
+
67
101
  // src/core/suggestions.ts
68
102
  var PROVIDER_PREFIXES = ["gpt-", "claude-", "gemini-", "deepseek-"];
69
103
  function getProviderPrefix(model) {
@@ -154,6 +188,7 @@ var SqliteStorage = class {
154
188
  session_id TEXT,
155
189
  user_id TEXT,
156
190
  feature TEXT,
191
+ app_id TEXT,
157
192
  timestamp TEXT NOT NULL
158
193
  )
159
194
  `);
@@ -170,13 +205,16 @@ var SqliteStorage = class {
170
205
  if (!cols.includes("cache_creation_tokens")) {
171
206
  this.db.exec(`ALTER TABLE usage ADD COLUMN cache_creation_tokens INTEGER NOT NULL DEFAULT 0`);
172
207
  }
208
+ if (!cols.includes("app_id")) {
209
+ this.db.exec(`ALTER TABLE usage ADD COLUMN app_id TEXT`);
210
+ }
173
211
  }
174
212
  record(entry) {
175
213
  this.db.prepare(
176
214
  `INSERT INTO usage
177
215
  (model, input_tokens, output_tokens, reasoning_tokens, cached_tokens, cache_creation_tokens,
178
- cost_usd, session_id, user_id, feature, timestamp)
179
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
216
+ cost_usd, session_id, user_id, feature, app_id, timestamp)
217
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
180
218
  ).run(
181
219
  entry.model,
182
220
  entry.inputTokens,
@@ -188,6 +226,7 @@ var SqliteStorage = class {
188
226
  entry.sessionId ?? null,
189
227
  entry.userId ?? null,
190
228
  entry.feature ?? null,
229
+ entry.appId ?? null,
191
230
  entry.timestamp
192
231
  );
193
232
  }
@@ -204,6 +243,7 @@ var SqliteStorage = class {
204
243
  ...r.session_id != null && { sessionId: r.session_id },
205
244
  ...r.user_id != null && { userId: r.user_id },
206
245
  ...r.feature != null && { feature: r.feature },
246
+ ...r.app_id != null && { appId: r.app_id },
207
247
  timestamp: r.timestamp
208
248
  }));
209
249
  }
@@ -269,7 +309,7 @@ async function getRemotePrices() {
269
309
 
270
310
  // prices.json
271
311
  var prices_default = {
272
- updated_at: "2026-04-24",
312
+ updated_at: "2026-06-09",
273
313
  source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
274
314
  models: {
275
315
  "gpt-4o": {
@@ -313,14 +353,12 @@ var prices_default = {
313
353
  input: 3,
314
354
  output: 15,
315
355
  cachedInput: 0.3,
316
- cacheCreationInput: 3.75,
317
- maxInputTokens: 1e6
356
+ maxInputTokens: 2e5
318
357
  },
319
358
  "claude-haiku-4-5": {
320
359
  input: 1,
321
360
  output: 5,
322
361
  cachedInput: 0.1,
323
- cacheCreationInput: 1.25,
324
362
  maxInputTokens: 2e5
325
363
  },
326
364
  "gemini-2.5-pro": {
@@ -372,7 +410,6 @@ var prices_default = {
372
410
  input: 3,
373
411
  output: 15,
374
412
  cachedInput: 0.3,
375
- cacheCreationInput: 3.75,
376
413
  maxInputTokens: 2e5
377
414
  },
378
415
  "gpt-oss-120b": {
@@ -865,9 +902,9 @@ var prices_default = {
865
902
  maxInputTokens: 163840
866
903
  },
867
904
  "deepseek-r1": {
868
- input: 0.55,
869
- output: 2.19,
870
- maxInputTokens: 65536
905
+ input: 1.35,
906
+ output: 5.4,
907
+ maxInputTokens: 128e3
871
908
  },
872
909
  "deepseek-v3": {
873
910
  input: 0.27,
@@ -1307,7 +1344,7 @@ var prices_default = {
1307
1344
  "deepseek-r1-distill-llama-70b": {
1308
1345
  input: 0.99,
1309
1346
  output: 0.99,
1310
- maxInputTokens: 8e3
1347
+ maxInputTokens: 32768
1311
1348
  },
1312
1349
  "deepseek-llama3.3-70b": {
1313
1350
  input: 0.2,
@@ -1591,7 +1628,97 @@ var prices_default = {
1591
1628
  input: 5,
1592
1629
  output: 30,
1593
1630
  cachedInput: 0.5,
1631
+ maxInputTokens: 105e4
1632
+ },
1633
+ "gpt-5.5-2026-04-23": {
1634
+ input: 5,
1635
+ output: 30,
1636
+ cachedInput: 0.5,
1637
+ maxInputTokens: 105e4
1638
+ },
1639
+ "gpt-5.5-pro": {
1640
+ input: 30,
1641
+ output: 180,
1642
+ cachedInput: 3,
1643
+ maxInputTokens: 105e4
1644
+ },
1645
+ "gpt-5.5-pro-2026-04-23": {
1646
+ input: 30,
1647
+ output: 180,
1648
+ cachedInput: 3,
1649
+ maxInputTokens: 105e4
1650
+ },
1651
+ "gpt-5.4-mini-2026-03-17": {
1652
+ input: 0.75,
1653
+ output: 4.5,
1654
+ cachedInput: 0.075,
1655
+ maxInputTokens: 272e3
1656
+ },
1657
+ "gpt-5.4-nano-2026-03-17": {
1658
+ input: 0.2,
1659
+ output: 1.25,
1660
+ cachedInput: 0.02,
1594
1661
  maxInputTokens: 272e3
1662
+ },
1663
+ "gpt-image-2": {
1664
+ input: 5,
1665
+ output: 10,
1666
+ cachedInput: 1.25
1667
+ },
1668
+ "gpt-image-2-2026-04-21": {
1669
+ input: 5,
1670
+ output: 10,
1671
+ cachedInput: 1.25
1672
+ },
1673
+ "gpt-realtime-2": {
1674
+ input: 4,
1675
+ output: 16,
1676
+ cachedInput: 0.4,
1677
+ maxInputTokens: 32e3
1678
+ },
1679
+ "gemini-3.5-flash": {
1680
+ input: 1.5,
1681
+ output: 9,
1682
+ cachedInput: 0.15,
1683
+ maxInputTokens: 1048576
1684
+ },
1685
+ "gemini-3.1-flash-lite": {
1686
+ input: 0.25,
1687
+ output: 1.5,
1688
+ cachedInput: 0.025,
1689
+ maxInputTokens: 1048576
1690
+ },
1691
+ "claude-opus-4-8": {
1692
+ input: 5,
1693
+ output: 25,
1694
+ cachedInput: 0.5,
1695
+ cacheCreationInput: 6.25,
1696
+ maxInputTokens: 1e6
1697
+ },
1698
+ "claude-opus-4-8@default": {
1699
+ input: 5,
1700
+ output: 25,
1701
+ cachedInput: 0.5,
1702
+ cacheCreationInput: 6.25,
1703
+ maxInputTokens: 1e6
1704
+ },
1705
+ "claude-4-sonnet": {
1706
+ input: 3,
1707
+ output: 15,
1708
+ cachedInput: 0.3,
1709
+ maxInputTokens: 2e5
1710
+ },
1711
+ "claude-4-opus": {
1712
+ input: 5,
1713
+ output: 25,
1714
+ cachedInput: 0.5,
1715
+ maxInputTokens: 2e5
1716
+ },
1717
+ "claude-3-7-sonnet": {
1718
+ input: 3,
1719
+ output: 15,
1720
+ cachedInput: 0.3,
1721
+ maxInputTokens: 2e5
1595
1722
  }
1596
1723
  }
1597
1724
  };
@@ -1631,7 +1758,10 @@ var TrackerConfigSchema = import_zod.z.object({
1631
1758
  windowHours: import_zod.z.number().positive().optional().default(24),
1632
1759
  mode: import_zod.z.enum(["once", "always"]).optional().default("once")
1633
1760
  }).optional(),
1634
- exporter: import_zod.z.custom((v) => v !== null && typeof v === "object" && typeof v.export === "function").optional()
1761
+ exporter: import_zod.z.custom((v) => v !== null && typeof v === "object" && typeof v.export === "function").optional(),
1762
+ appId: import_zod.z.string().optional(),
1763
+ cloudApiKey: import_zod.z.string().optional(),
1764
+ cloudEndpoint: import_zod.z.string().url().optional()
1635
1765
  });
1636
1766
  function createTracker(config = {}) {
1637
1767
  const parsed = TrackerConfigSchema.safeParse(config);
@@ -1650,9 +1780,13 @@ ${issues}`);
1650
1780
  budgets,
1651
1781
  suggestions,
1652
1782
  anomalyDetection,
1653
- exporter
1783
+ exporter,
1784
+ appId,
1785
+ cloudApiKey,
1786
+ cloudEndpoint
1654
1787
  } = parsed.data;
1655
1788
  const storage = typeof storageOption === "object" ? storageOption : createStorage(storageOption);
1789
+ const cloudExporter = cloudApiKey ? new CloudExporter(cloudApiKey, cloudEndpoint) : null;
1656
1790
  let remotePrices;
1657
1791
  let pricesUpdatedAt = bundledUpdatedAt;
1658
1792
  if (syncPrices) {
@@ -1705,13 +1839,17 @@ ${issues}`);
1705
1839
  const full = {
1706
1840
  ...entry,
1707
1841
  costUSD,
1708
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1842
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1843
+ ...appId !== void 0 && entry.appId === void 0 && { appId }
1709
1844
  };
1710
1845
  storage.record(full);
1711
1846
  if (exporter) {
1712
1847
  Promise.resolve(exporter.export(full)).catch(() => {
1713
1848
  });
1714
1849
  }
1850
+ if (cloudExporter) {
1851
+ cloudExporter.export(full);
1852
+ }
1715
1853
  maybeFireAlerts(full);
1716
1854
  if (anomalyDetection) maybeDetectAnomaly(full);
1717
1855
  if (suggestions) {
@@ -1792,6 +1930,7 @@ ${issues}`);
1792
1930
  const bySession = {};
1793
1931
  const byUser = {};
1794
1932
  const byFeature = {};
1933
+ const byApp = {};
1795
1934
  let totalInput = 0;
1796
1935
  let totalOutput = 0;
1797
1936
  let totalCost = 0;
@@ -1828,6 +1967,11 @@ ${issues}`);
1828
1967
  f.costUSD += e.costUSD;
1829
1968
  f.calls += 1;
1830
1969
  }
1970
+ if (e.appId) {
1971
+ const a = byApp[e.appId] ??= { costUSD: 0, calls: 0 };
1972
+ a.costUSD += e.costUSD;
1973
+ a.calls += 1;
1974
+ }
1831
1975
  }
1832
1976
  if (options && entries.length > 0) {
1833
1977
  periodFrom = entries[0]?.timestamp ?? periodFrom;
@@ -1839,6 +1983,7 @@ ${issues}`);
1839
1983
  bySession,
1840
1984
  byUser,
1841
1985
  byFeature,
1986
+ byApp,
1842
1987
  period: { from: periodFrom, to: lastTimestamp },
1843
1988
  ...pricesUpdatedAt ? { pricesUpdatedAt } : {}
1844
1989
  };
@@ -1943,7 +2088,7 @@ ${issues}`);
1943
2088
  }
1944
2089
  async function exportCSV() {
1945
2090
  const entries = await Promise.resolve(storage.getAll());
1946
- const header = "timestamp,model,inputTokens,outputTokens,reasoningTokens,cachedTokens,cacheCreationTokens,costUSD,sessionId,userId,feature";
2091
+ const header = "timestamp,model,inputTokens,outputTokens,reasoningTokens,cachedTokens,cacheCreationTokens,costUSD,sessionId,userId,feature,appId";
1947
2092
  const rows = entries.map(
1948
2093
  (e) => [
1949
2094
  csvEscape(e.timestamp),
@@ -1956,7 +2101,8 @@ ${issues}`);
1956
2101
  e.costUSD.toFixed(8),
1957
2102
  csvEscape(e.sessionId ?? ""),
1958
2103
  csvEscape(e.userId ?? ""),
1959
- csvEscape(e.feature ?? "")
2104
+ csvEscape(e.feature ?? ""),
2105
+ csvEscape(e.appId ?? "")
1960
2106
  ].join(",")
1961
2107
  );
1962
2108
  return [header, ...rows].join("\n");
@@ -2017,7 +2163,7 @@ function csvEscape(value) {
2017
2163
  }
2018
2164
 
2019
2165
  // src/core/lazy-tracker.ts
2020
- var CSV_HEADER = "timestamp,model,inputTokens,outputTokens,reasoningTokens,cachedTokens,cacheCreationTokens,costUSD,sessionId,userId,feature";
2166
+ var CSV_HEADER = "timestamp,model,inputTokens,outputTokens,reasoningTokens,cachedTokens,cacheCreationTokens,costUSD,sessionId,userId,feature,appId";
2021
2167
  function emptyReport() {
2022
2168
  const now = (/* @__PURE__ */ new Date()).toISOString();
2023
2169
  return {
@@ -2027,6 +2173,7 @@ function emptyReport() {
2027
2173
  bySession: {},
2028
2174
  byUser: {},
2029
2175
  byFeature: {},
2176
+ byApp: {},
2030
2177
  period: { from: now, to: now }
2031
2178
  };
2032
2179
  }