@diogonzafe/tokenwatch 0.4.0 → 0.6.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.
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { T as TrackerConfig, a as Tracker, L as LazyTracker, b as TrackingMeta } from './index-CJKk1hHw.cjs';
2
- export { B as BudgetConfig, C as CostForecast, F as FeatureStats, c as ForecastOptions, I as IStorage, M as ModelPrice, d as ModelStats, P as PriceMap, e as PricesFile, R as Report, f as ReportOptions, S as SessionStats, U as UsageEntry, g as UserStats } from './index-CJKk1hHw.cjs';
1
+ import { T as TrackerConfig, a as Tracker, L as LazyTracker, b as TrackingMeta } from './index-D9xq0RNg.cjs';
2
+ export { A as AnomalyDetectionConfig, B as BudgetConfig, C as CostForecast, F as FeatureStats, c as ForecastOptions, I as IExporter, d as IStorage, M as ModelPrice, e as ModelStats, P as PriceMap, f as PricesFile, R as Report, g as ReportOptions, S as SessionStats, U as UsageEntry, h as UserStats } from './index-D9xq0RNg.cjs';
3
3
 
4
4
  declare function createTracker(config?: TrackerConfig): Tracker;
5
5
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { T as TrackerConfig, a as Tracker, L as LazyTracker, b as TrackingMeta } from './index-CJKk1hHw.js';
2
- export { B as BudgetConfig, C as CostForecast, F as FeatureStats, c as ForecastOptions, I as IStorage, M as ModelPrice, d as ModelStats, P as PriceMap, e as PricesFile, R as Report, f as ReportOptions, S as SessionStats, U as UsageEntry, g as UserStats } from './index-CJKk1hHw.js';
1
+ import { T as TrackerConfig, a as Tracker, L as LazyTracker, b as TrackingMeta } from './index-D9xq0RNg.js';
2
+ export { A as AnomalyDetectionConfig, B as BudgetConfig, C as CostForecast, F as FeatureStats, c as ForecastOptions, I as IExporter, d as IStorage, M as ModelPrice, e as ModelStats, P as PriceMap, f as PricesFile, R as Report, g as ReportOptions, S as SessionStats, U as UsageEntry, h as UserStats } from './index-D9xq0RNg.js';
3
3
 
4
4
  declare function createTracker(config?: TrackerConfig): Tracker;
5
5
 
package/dist/index.js CHANGED
@@ -237,7 +237,7 @@ async function getRemotePrices() {
237
237
 
238
238
  // prices.json
239
239
  var prices_default = {
240
- updated_at: "2026-04-22",
240
+ updated_at: "2026-04-23",
241
241
  source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
242
242
  models: {
243
243
  "gpt-4o": {
@@ -1586,7 +1586,14 @@ var TrackerConfigSchema = z.object({
1586
1586
  perUser: BudgetConfigSchema.optional(),
1587
1587
  perSession: BudgetConfigSchema.optional()
1588
1588
  }).optional(),
1589
- suggestions: z.boolean().optional().default(false)
1589
+ suggestions: z.boolean().optional().default(false),
1590
+ anomalyDetection: z.object({
1591
+ multiplierThreshold: z.number().positive(),
1592
+ webhookUrl: z.string().url(),
1593
+ windowHours: z.number().positive().optional().default(24),
1594
+ mode: z.enum(["once", "always"]).optional().default("once")
1595
+ }).optional(),
1596
+ exporter: z.custom((v) => v !== null && typeof v === "object" && typeof v.export === "function").optional()
1590
1597
  });
1591
1598
  function createTracker(config = {}) {
1592
1599
  const parsed = TrackerConfigSchema.safeParse(config);
@@ -1603,7 +1610,9 @@ ${issues}`);
1603
1610
  customPrices,
1604
1611
  warnIfStaleAfterHours,
1605
1612
  budgets,
1606
- suggestions
1613
+ suggestions,
1614
+ anomalyDetection,
1615
+ exporter
1607
1616
  } = parsed.data;
1608
1617
  const storage = typeof storageOption === "object" ? storageOption : createStorage(storageOption);
1609
1618
  let remotePrices;
@@ -1636,6 +1645,7 @@ ${issues}`);
1636
1645
  let alertFired = false;
1637
1646
  const firedUserAlerts = /* @__PURE__ */ new Set();
1638
1647
  const firedSessionAlerts = /* @__PURE__ */ new Set();
1648
+ const firedAnomalyKeys = /* @__PURE__ */ new Set();
1639
1649
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1640
1650
  function resolveModelPrice(model) {
1641
1651
  maybeWarnStaleness();
@@ -1660,7 +1670,12 @@ ${issues}`);
1660
1670
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1661
1671
  };
1662
1672
  storage.record(full);
1673
+ if (exporter) {
1674
+ Promise.resolve(exporter.export(full)).catch(() => {
1675
+ });
1676
+ }
1663
1677
  maybeFireAlerts(full);
1678
+ if (anomalyDetection) maybeDetectAnomaly(full);
1664
1679
  if (suggestions) {
1665
1680
  maybeSuggestCheaperModel(entry.model, costUSD, entry.inputTokens, entry.outputTokens, {
1666
1681
  bundledPrices,
@@ -1830,11 +1845,56 @@ ${issues}`);
1830
1845
  basedOnPeriod: { from: first, to: last }
1831
1846
  };
1832
1847
  }
1848
+ function maybeDetectAnomaly(entry) {
1849
+ if (entry.costUSD <= 0) return;
1850
+ const { multiplierThreshold, webhookUrl: aUrl, windowHours: wh, mode: modeRaw } = anomalyDetection;
1851
+ const wHours = wh ?? 24;
1852
+ const mode = modeRaw ?? "once";
1853
+ const windowStart = Date.now() - wHours * 60 * 60 * 1e3;
1854
+ const entryTs = new Date(entry.timestamp).getTime();
1855
+ function checkEntity(key, label, predicate) {
1856
+ if (mode !== "always" && firedAnomalyKeys.has(key)) return;
1857
+ if (mode !== "always") firedAnomalyKeys.add(key);
1858
+ Promise.resolve(storage.getAll()).then((all) => {
1859
+ const history = all.filter(
1860
+ (e) => predicate(e) && new Date(e.timestamp).getTime() >= windowStart && new Date(e.timestamp).getTime() !== entryTs
1861
+ );
1862
+ if (history.length === 0) {
1863
+ if (mode !== "always") firedAnomalyKeys.delete(key);
1864
+ return;
1865
+ }
1866
+ const avg = history.reduce((s, e) => s + e.costUSD, 0) / history.length;
1867
+ if (avg <= 0 || entry.costUSD <= avg * multiplierThreshold) {
1868
+ if (mode !== "always") firedAnomalyKeys.delete(key);
1869
+ return;
1870
+ }
1871
+ const multiple = (entry.costUSD / avg).toFixed(1);
1872
+ fireWebhook(aUrl, {
1873
+ text: `[tokenwatch] Anomaly: ${label} call cost $${entry.costUSD.toFixed(4)} is ${multiple}x above ${wHours}h average ($${avg.toFixed(4)})`
1874
+ });
1875
+ }).catch(() => {
1876
+ if (mode !== "always") firedAnomalyKeys.delete(key);
1877
+ });
1878
+ }
1879
+ if (entry.userId) {
1880
+ checkEntity(
1881
+ `user:${entry.userId}`,
1882
+ `user "${entry.userId}"`,
1883
+ (e) => e.userId === entry.userId
1884
+ );
1885
+ }
1886
+ checkEntity(
1887
+ `model:${entry.model}`,
1888
+ `model "${entry.model}"`,
1889
+ (e) => e.model === entry.model
1890
+ );
1891
+ }
1833
1892
  async function reset() {
1834
1893
  await Promise.resolve(storage.clearAll());
1835
1894
  alertFired = false;
1836
1895
  firedUserAlerts.clear();
1837
1896
  firedSessionAlerts.clear();
1897
+ firedAnomalyKeys.clear();
1838
1898
  }
1839
1899
  async function resetSession(sessionId) {
1840
1900
  await Promise.resolve(storage.clearSession(sessionId));