@diogonzafe/tokenwatch 0.9.0 → 0.10.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-fD5QLTWg.cjs';
1
+ import { I as IExporter, U as UsageEntry } from './index-B5OF0YCl.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-fD5QLTWg.js';
1
+ import { I as IExporter, U as UsageEntry } from './index-B5OF0YCl.js';
2
2
 
3
3
  interface OTelAttributes {
4
4
  [key: string]: string | number | boolean | undefined;
@@ -90,6 +90,8 @@ interface UsageEntry {
90
90
  feature?: string;
91
91
  /** Application identifier — set once in TrackerConfig.appId and stamped on every entry */
92
92
  appId?: string;
93
+ /** Arbitrary key-value tags for custom dimensions (e.g. tenantId, region, plan) */
94
+ metadata?: Record<string, string>;
93
95
  timestamp: string;
94
96
  }
95
97
  interface ModelStats {
@@ -137,6 +139,11 @@ interface Report {
137
139
  byUser: Record<string, UserStats>;
138
140
  byFeature: Record<string, FeatureStats>;
139
141
  byApp: Record<string, AppStats>;
142
+ /** Grouped by each custom metadata key → value → stats */
143
+ byMetadata: Record<string, Record<string, {
144
+ costUSD: number;
145
+ calls: number;
146
+ }>>;
140
147
  period: {
141
148
  from: string;
142
149
  to: string;
@@ -192,6 +199,8 @@ interface TrackingMeta {
192
199
  __feature?: string;
193
200
  /** Override the tracker-level appId for this specific call */
194
201
  __appId?: string;
202
+ /** Arbitrary key-value tags for custom dimensions — appear in report.byMetadata */
203
+ __metadata?: Record<string, string>;
195
204
  }
196
205
 
197
206
  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 };
@@ -90,6 +90,8 @@ interface UsageEntry {
90
90
  feature?: string;
91
91
  /** Application identifier — set once in TrackerConfig.appId and stamped on every entry */
92
92
  appId?: string;
93
+ /** Arbitrary key-value tags for custom dimensions (e.g. tenantId, region, plan) */
94
+ metadata?: Record<string, string>;
93
95
  timestamp: string;
94
96
  }
95
97
  interface ModelStats {
@@ -137,6 +139,11 @@ interface Report {
137
139
  byUser: Record<string, UserStats>;
138
140
  byFeature: Record<string, FeatureStats>;
139
141
  byApp: Record<string, AppStats>;
142
+ /** Grouped by each custom metadata key → value → stats */
143
+ byMetadata: Record<string, Record<string, {
144
+ costUSD: number;
145
+ calls: number;
146
+ }>>;
140
147
  period: {
141
148
  from: string;
142
149
  to: string;
@@ -192,6 +199,8 @@ interface TrackingMeta {
192
199
  __feature?: string;
193
200
  /** Override the tracker-level appId for this specific call */
194
201
  __appId?: string;
202
+ /** Arbitrary key-value tags for custom dimensions — appear in report.byMetadata */
203
+ __metadata?: Record<string, string>;
195
204
  }
196
205
 
197
206
  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
@@ -91,6 +91,7 @@ var CloudExporter = class {
91
91
  sessionId: entry.sessionId,
92
92
  userId: entry.userId,
93
93
  feature: entry.feature,
94
+ metadata: entry.metadata,
94
95
  timestamp: entry.timestamp
95
96
  })
96
97
  }).catch(() => {
@@ -189,6 +190,7 @@ var SqliteStorage = class {
189
190
  user_id TEXT,
190
191
  feature TEXT,
191
192
  app_id TEXT,
193
+ metadata TEXT,
192
194
  timestamp TEXT NOT NULL
193
195
  )
194
196
  `);
@@ -208,13 +210,16 @@ var SqliteStorage = class {
208
210
  if (!cols.includes("app_id")) {
209
211
  this.db.exec(`ALTER TABLE usage ADD COLUMN app_id TEXT`);
210
212
  }
213
+ if (!cols.includes("metadata")) {
214
+ this.db.exec(`ALTER TABLE usage ADD COLUMN metadata TEXT`);
215
+ }
211
216
  }
212
217
  record(entry) {
213
218
  this.db.prepare(
214
219
  `INSERT INTO usage
215
220
  (model, input_tokens, output_tokens, reasoning_tokens, cached_tokens, cache_creation_tokens,
216
- cost_usd, session_id, user_id, feature, app_id, timestamp)
217
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
221
+ cost_usd, session_id, user_id, feature, app_id, metadata, timestamp)
222
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
218
223
  ).run(
219
224
  entry.model,
220
225
  entry.inputTokens,
@@ -227,6 +232,7 @@ var SqliteStorage = class {
227
232
  entry.userId ?? null,
228
233
  entry.feature ?? null,
229
234
  entry.appId ?? null,
235
+ entry.metadata != null ? JSON.stringify(entry.metadata) : null,
230
236
  entry.timestamp
231
237
  );
232
238
  }
@@ -244,6 +250,7 @@ var SqliteStorage = class {
244
250
  ...r.user_id != null && { userId: r.user_id },
245
251
  ...r.feature != null && { feature: r.feature },
246
252
  ...r.app_id != null && { appId: r.app_id },
253
+ ...r.metadata != null && { metadata: JSON.parse(r.metadata) },
247
254
  timestamp: r.timestamp
248
255
  }));
249
256
  }
@@ -1931,6 +1938,7 @@ ${issues}`);
1931
1938
  const byUser = {};
1932
1939
  const byFeature = {};
1933
1940
  const byApp = {};
1941
+ const byMetadata = {};
1934
1942
  let totalInput = 0;
1935
1943
  let totalOutput = 0;
1936
1944
  let totalCost = 0;
@@ -1972,6 +1980,14 @@ ${issues}`);
1972
1980
  a.costUSD += e.costUSD;
1973
1981
  a.calls += 1;
1974
1982
  }
1983
+ if (e.metadata) {
1984
+ for (const [key, val] of Object.entries(e.metadata)) {
1985
+ const group = byMetadata[key] ??= {};
1986
+ const slot = group[val] ??= { costUSD: 0, calls: 0 };
1987
+ slot.costUSD += e.costUSD;
1988
+ slot.calls += 1;
1989
+ }
1990
+ }
1975
1991
  }
1976
1992
  if (options && entries.length > 0) {
1977
1993
  periodFrom = entries[0]?.timestamp ?? periodFrom;
@@ -1984,6 +2000,7 @@ ${issues}`);
1984
2000
  byUser,
1985
2001
  byFeature,
1986
2002
  byApp,
2003
+ byMetadata,
1987
2004
  period: { from: periodFrom, to: lastTimestamp },
1988
2005
  ...pricesUpdatedAt ? { pricesUpdatedAt } : {}
1989
2006
  };
@@ -2088,7 +2105,7 @@ ${issues}`);
2088
2105
  }
2089
2106
  async function exportCSV() {
2090
2107
  const entries = await Promise.resolve(storage.getAll());
2091
- const header = "timestamp,model,inputTokens,outputTokens,reasoningTokens,cachedTokens,cacheCreationTokens,costUSD,sessionId,userId,feature,appId";
2108
+ const header = "timestamp,model,inputTokens,outputTokens,reasoningTokens,cachedTokens,cacheCreationTokens,costUSD,sessionId,userId,feature,appId,metadata";
2092
2109
  const rows = entries.map(
2093
2110
  (e) => [
2094
2111
  csvEscape(e.timestamp),
@@ -2102,7 +2119,8 @@ ${issues}`);
2102
2119
  csvEscape(e.sessionId ?? ""),
2103
2120
  csvEscape(e.userId ?? ""),
2104
2121
  csvEscape(e.feature ?? ""),
2105
- csvEscape(e.appId ?? "")
2122
+ csvEscape(e.appId ?? ""),
2123
+ csvEscape(e.metadata ? JSON.stringify(e.metadata) : "")
2106
2124
  ].join(",")
2107
2125
  );
2108
2126
  return [header, ...rows].join("\n");
@@ -2174,6 +2192,7 @@ function emptyReport() {
2174
2192
  byUser: {},
2175
2193
  byFeature: {},
2176
2194
  byApp: {},
2195
+ byMetadata: {},
2177
2196
  period: { from: now, to: now }
2178
2197
  };
2179
2198
  }
@@ -2230,12 +2249,13 @@ function createLazyTracker() {
2230
2249
 
2231
2250
  // src/providers/openai.ts
2232
2251
  function extractMeta(params) {
2233
- const { __sessionId, __userId, __feature, ...cleaned } = params;
2252
+ const { __sessionId, __userId, __feature, __metadata, ...cleaned } = params;
2234
2253
  return {
2235
2254
  cleaned,
2236
2255
  sessionId: typeof __sessionId === "string" ? __sessionId : void 0,
2237
2256
  userId: typeof __userId === "string" ? __userId : void 0,
2238
- feature: typeof __feature === "string" ? __feature : void 0
2257
+ feature: typeof __feature === "string" ? __feature : void 0,
2258
+ metadata: __metadata != null && typeof __metadata === "object" ? __metadata : void 0
2239
2259
  };
2240
2260
  }
2241
2261
  function extractUsage(usage) {
@@ -2250,7 +2270,7 @@ function extractUsage(usage) {
2250
2270
  cachedTokens
2251
2271
  };
2252
2272
  }
2253
- function trackWithMeta(tracker, model, inputTokens, outputTokens, reasoningTokens, sessionId, userId, feature, cachedTokens = 0) {
2273
+ function trackWithMeta(tracker, model, inputTokens, outputTokens, reasoningTokens, sessionId, userId, feature, cachedTokens = 0, metadata) {
2254
2274
  tracker.track({
2255
2275
  model,
2256
2276
  inputTokens,
@@ -2259,10 +2279,11 @@ function trackWithMeta(tracker, model, inputTokens, outputTokens, reasoningToken
2259
2279
  ...cachedTokens > 0 && { cachedTokens },
2260
2280
  ...sessionId !== void 0 && { sessionId },
2261
2281
  ...userId !== void 0 && { userId },
2262
- ...feature !== void 0 && { feature }
2282
+ ...feature !== void 0 && { feature },
2283
+ ...metadata !== void 0 && { metadata }
2263
2284
  });
2264
2285
  }
2265
- async function* wrapStream(stream, model, sessionId, userId, feature, tracker) {
2286
+ async function* wrapStream(stream, model, sessionId, userId, feature, tracker, metadata) {
2266
2287
  let lastChunk;
2267
2288
  for await (const chunk of stream) {
2268
2289
  lastChunk = chunk;
@@ -2274,7 +2295,7 @@ async function* wrapStream(stream, model, sessionId, userId, feature, tracker) {
2274
2295
  `[tokenwatch] No usage data in stream for model "${model}". Cost recorded as $0. Pass stream_options: { include_usage: true } to get accurate costs.`
2275
2296
  );
2276
2297
  }
2277
- trackWithMeta(tracker, model, inputTokens, outputTokens, reasoningTokens, sessionId, userId, feature, cachedTokens);
2298
+ trackWithMeta(tracker, model, inputTokens, outputTokens, reasoningTokens, sessionId, userId, feature, cachedTokens, metadata);
2278
2299
  }
2279
2300
  function wrapOpenAI(client, tracker) {
2280
2301
  const proxiedCompletions = new Proxy(client.chat.completions, {
@@ -2282,7 +2303,7 @@ function wrapOpenAI(client, tracker) {
2282
2303
  if (prop !== "create")
2283
2304
  return target[prop];
2284
2305
  return async function(params) {
2285
- const { cleaned, sessionId, userId, feature } = extractMeta(params);
2306
+ const { cleaned, sessionId, userId, feature, metadata } = extractMeta(params);
2286
2307
  const model = typeof cleaned["model"] === "string" ? cleaned["model"] : "unknown";
2287
2308
  const result = await target.create(cleaned);
2288
2309
  if (result && typeof result === "object" && Symbol.asyncIterator in result) {
@@ -2292,7 +2313,8 @@ function wrapOpenAI(client, tracker) {
2292
2313
  sessionId,
2293
2314
  userId,
2294
2315
  feature,
2295
- tracker
2316
+ tracker,
2317
+ metadata
2296
2318
  );
2297
2319
  }
2298
2320
  const completion = result;
@@ -2306,7 +2328,8 @@ function wrapOpenAI(client, tracker) {
2306
2328
  sessionId,
2307
2329
  userId,
2308
2330
  feature,
2309
- cachedTokens
2331
+ cachedTokens,
2332
+ metadata
2310
2333
  );
2311
2334
  return result;
2312
2335
  };
@@ -2323,12 +2346,12 @@ function wrapOpenAI(client, tracker) {
2323
2346
  if (prop !== "create")
2324
2347
  return target[prop];
2325
2348
  return async function(params) {
2326
- const { cleaned, sessionId, userId, feature } = extractMeta(params);
2349
+ const { cleaned, sessionId, userId, feature, metadata } = extractMeta(params);
2327
2350
  const model = typeof cleaned["model"] === "string" ? cleaned["model"] : "unknown";
2328
2351
  const result = await target.create(cleaned);
2329
2352
  const embedding = result;
2330
2353
  const inputTokens = embedding.usage?.total_tokens ?? 0;
2331
- trackWithMeta(tracker, embedding.model ?? model, inputTokens, 0, 0, sessionId, userId, feature);
2354
+ trackWithMeta(tracker, embedding.model ?? model, inputTokens, 0, 0, sessionId, userId, feature, 0, metadata);
2332
2355
  return result;
2333
2356
  };
2334
2357
  }
@@ -2344,12 +2367,13 @@ function wrapOpenAI(client, tracker) {
2344
2367
 
2345
2368
  // src/providers/anthropic.ts
2346
2369
  function extractMeta2(params) {
2347
- const { __sessionId, __userId, __feature, ...cleaned } = params;
2370
+ const { __sessionId, __userId, __feature, __metadata, ...cleaned } = params;
2348
2371
  return {
2349
2372
  cleaned,
2350
2373
  sessionId: typeof __sessionId === "string" ? __sessionId : void 0,
2351
2374
  userId: typeof __userId === "string" ? __userId : void 0,
2352
- feature: typeof __feature === "string" ? __feature : void 0
2375
+ feature: typeof __feature === "string" ? __feature : void 0,
2376
+ metadata: __metadata != null && typeof __metadata === "object" ? __metadata : void 0
2353
2377
  };
2354
2378
  }
2355
2379
  function extractUsage2(usage) {
@@ -2366,7 +2390,7 @@ function extractThinkingTokenApprox(content) {
2366
2390
  const chars = content.filter((b) => b.type === "thinking").reduce((sum, b) => sum + (b.thinking?.length ?? 0), 0);
2367
2391
  return chars > 0 ? Math.round(chars / 4) : 0;
2368
2392
  }
2369
- function trackWithMeta2(tracker, model, inputTokens, outputTokens, reasoningTokens, sessionId, userId, feature, cachedTokens = 0, cacheCreationTokens = 0) {
2393
+ function trackWithMeta2(tracker, model, inputTokens, outputTokens, reasoningTokens, sessionId, userId, feature, cachedTokens = 0, cacheCreationTokens = 0, metadata) {
2370
2394
  tracker.track({
2371
2395
  model,
2372
2396
  inputTokens,
@@ -2376,10 +2400,11 @@ function trackWithMeta2(tracker, model, inputTokens, outputTokens, reasoningToke
2376
2400
  ...cacheCreationTokens > 0 && { cacheCreationTokens },
2377
2401
  ...sessionId !== void 0 && { sessionId },
2378
2402
  ...userId !== void 0 && { userId },
2379
- ...feature !== void 0 && { feature }
2403
+ ...feature !== void 0 && { feature },
2404
+ ...metadata !== void 0 && { metadata }
2380
2405
  });
2381
2406
  }
2382
- async function* wrapStream2(stream, model, sessionId, userId, feature, tracker) {
2407
+ async function* wrapStream2(stream, model, sessionId, userId, feature, tracker, metadata) {
2383
2408
  let inputTokens = 0;
2384
2409
  let outputTokens = 0;
2385
2410
  let cachedTokens = 0;
@@ -2407,7 +2432,7 @@ async function* wrapStream2(stream, model, sessionId, userId, feature, tracker)
2407
2432
  }
2408
2433
  }
2409
2434
  const reasoningTokens = thinkingCharCount > 0 ? Math.round(thinkingCharCount / 4) : 0;
2410
- trackWithMeta2(tracker, model, inputTokens, outputTokens, reasoningTokens, sessionId, userId, feature, cachedTokens, cacheCreationTokens);
2435
+ trackWithMeta2(tracker, model, inputTokens, outputTokens, reasoningTokens, sessionId, userId, feature, cachedTokens, cacheCreationTokens, metadata);
2411
2436
  }
2412
2437
  function wrapAnthropic(client, tracker) {
2413
2438
  const proxiedMessages = new Proxy(client.messages, {
@@ -2415,7 +2440,7 @@ function wrapAnthropic(client, tracker) {
2415
2440
  if (prop !== "create")
2416
2441
  return target[prop];
2417
2442
  return async function(params) {
2418
- const { cleaned, sessionId, userId, feature } = extractMeta2(params);
2443
+ const { cleaned, sessionId, userId, feature, metadata } = extractMeta2(params);
2419
2444
  const model = typeof cleaned["model"] === "string" ? cleaned["model"] : "unknown";
2420
2445
  const result = await target.create(cleaned);
2421
2446
  if (result && typeof result === "object" && Symbol.asyncIterator in result) {
@@ -2425,7 +2450,8 @@ function wrapAnthropic(client, tracker) {
2425
2450
  sessionId,
2426
2451
  userId,
2427
2452
  feature,
2428
- tracker
2453
+ tracker,
2454
+ metadata
2429
2455
  );
2430
2456
  }
2431
2457
  const message = result;
@@ -2441,7 +2467,8 @@ function wrapAnthropic(client, tracker) {
2441
2467
  userId,
2442
2468
  feature,
2443
2469
  cachedTokens,
2444
- cacheCreationTokens
2470
+ cacheCreationTokens,
2471
+ metadata
2445
2472
  );
2446
2473
  return result;
2447
2474
  };
@@ -2462,10 +2489,11 @@ function wrapGemini(client, tracker) {
2462
2489
  if (prop !== "getGenerativeModel")
2463
2490
  return target[prop];
2464
2491
  return function(modelParams) {
2465
- const { __sessionId, __userId, __feature, ...cleanedParams } = modelParams;
2492
+ const { __sessionId, __userId, __feature, __metadata, ...cleanedParams } = modelParams;
2466
2493
  const feature = typeof __feature === "string" ? __feature : void 0;
2467
2494
  const sessionId = typeof __sessionId === "string" ? __sessionId : void 0;
2468
2495
  const userId = typeof __userId === "string" ? __userId : void 0;
2496
+ const metadata = __metadata != null && typeof __metadata === "object" ? __metadata : void 0;
2469
2497
  const modelInstance = target.getGenerativeModel(cleanedParams);
2470
2498
  const modelId = modelParams.model;
2471
2499
  return new Proxy(modelInstance, {
@@ -2480,7 +2508,8 @@ function wrapGemini(client, tracker) {
2480
2508
  outputTokens: meta?.candidatesTokenCount ?? 0,
2481
2509
  ...sessionId !== void 0 && { sessionId },
2482
2510
  ...userId !== void 0 && { userId },
2483
- ...feature !== void 0 && { feature }
2511
+ ...feature !== void 0 && { feature },
2512
+ ...metadata !== void 0 && { metadata }
2484
2513
  });
2485
2514
  return result;
2486
2515
  };
@@ -2496,7 +2525,8 @@ function wrapGemini(client, tracker) {
2496
2525
  outputTokens: meta?.candidatesTokenCount ?? 0,
2497
2526
  ...sessionId !== void 0 && { sessionId },
2498
2527
  ...userId !== void 0 && { userId },
2499
- ...feature !== void 0 && { feature }
2528
+ ...feature !== void 0 && { feature },
2529
+ ...metadata !== void 0 && { metadata }
2500
2530
  });
2501
2531
  }).catch(() => {
2502
2532
  });