@diogonzafe/tokenwatch 0.1.17 → 0.2.1
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/README.md +58 -6
- package/dist/adapters.cjs +56 -22
- package/dist/adapters.cjs.map +1 -1
- package/dist/adapters.d.cts +7 -3
- package/dist/adapters.d.ts +7 -3
- package/dist/adapters.js +56 -22
- package/dist/adapters.js.map +1 -1
- package/dist/cli.js +42 -13
- package/dist/cli.js.map +1 -1
- package/dist/{index-Cy_sl3FI.d.ts → index-B_EmA3K7.d.cts} +15 -1
- package/dist/{index-Cy_sl3FI.d.cts → index-B_EmA3K7.d.ts} +15 -1
- package/dist/index.cjs +117 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -8
- package/dist/index.d.ts +23 -8
- package/dist/index.js +117 -37
- package/dist/index.js.map +1 -1
- package/package.json +1 -2
- package/dist/cli.cjs +0 -1625
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.d.cts +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @diogonzafe/tokenwatch
|
|
2
2
|
|
|
3
|
-
Transparent TypeScript wrapper that intercepts LLM API calls and tracks cost in real-time by session, user and
|
|
3
|
+
Transparent TypeScript wrapper that intercepts LLM API calls and tracks cost in real-time by session, user, model and feature — without changing anything in your existing code.
|
|
4
4
|
|
|
5
5
|
Supports **OpenAI**, **Anthropic**, **Google Gemini** and **DeepSeek**.
|
|
6
6
|
|
|
@@ -59,6 +59,7 @@ const res = await openai.chat.completions.create({
|
|
|
59
59
|
// Optional — removed before sending to the API
|
|
60
60
|
__sessionId: 'session_abc',
|
|
61
61
|
__userId: 'user_123',
|
|
62
|
+
__feature: 'chat', // tag calls by product feature → report.byFeature
|
|
62
63
|
})
|
|
63
64
|
// res is identical to the original OpenAI response — zero difference
|
|
64
65
|
```
|
|
@@ -79,6 +80,20 @@ for await (const chunk of stream) {
|
|
|
79
80
|
// Cost tracked automatically from the final chunk
|
|
80
81
|
```
|
|
81
82
|
|
|
83
|
+
Embeddings are also tracked automatically:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
const embedding = await openai.embeddings.create({
|
|
87
|
+
model: 'text-embedding-3-small',
|
|
88
|
+
input: 'The food was delicious',
|
|
89
|
+
__sessionId: 'session_abc',
|
|
90
|
+
__feature: 'rag', // optional
|
|
91
|
+
})
|
|
92
|
+
// inputTokens = usage.total_tokens, outputTokens = 0
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
> **Note:** `wrapOpenAI` covers `chat.completions` and `embeddings`. Other endpoints (e.g. fine-tuning, images) are not intercepted — use `tracker.track()` manually if needed.
|
|
96
|
+
|
|
82
97
|
---
|
|
83
98
|
|
|
84
99
|
## Anthropic
|
|
@@ -95,6 +110,7 @@ const res = await anthropic.messages.create({
|
|
|
95
110
|
messages: [{ role: 'user', content: 'Hello' }],
|
|
96
111
|
__sessionId: 'session_abc',
|
|
97
112
|
__userId: 'user_123',
|
|
113
|
+
__feature: 'summarizer',
|
|
98
114
|
})
|
|
99
115
|
```
|
|
100
116
|
|
|
@@ -108,7 +124,12 @@ import { wrapGemini } from '@diogonzafe/tokenwatch'
|
|
|
108
124
|
|
|
109
125
|
const genAI = wrapGemini(new GoogleGenerativeAI(process.env.GEMINI_API_KEY!), tracker)
|
|
110
126
|
|
|
111
|
-
|
|
127
|
+
// __sessionId, __userId, __feature are passed to getGenerativeModel (not per-call)
|
|
128
|
+
const model = genAI.getGenerativeModel({
|
|
129
|
+
model: 'gemini-2.5-flash',
|
|
130
|
+
__sessionId: 'session_abc',
|
|
131
|
+
__feature: 'rag',
|
|
132
|
+
})
|
|
112
133
|
const result = await model.generateContent('Explain quantum computing')
|
|
113
134
|
```
|
|
114
135
|
|
|
@@ -148,11 +169,13 @@ const report = await tracker.getReport()
|
|
|
148
169
|
// totalCostUSD: 0.087,
|
|
149
170
|
// totalTokens: { input: 24000, output: 6000 },
|
|
150
171
|
// byModel: {
|
|
151
|
-
// 'gpt-4o':
|
|
152
|
-
// '
|
|
172
|
+
// 'gpt-4o': { costUSD: 0.062, calls: 5, tokens: { input: 20000, output: 5000, reasoning: 0 } },
|
|
173
|
+
// 'o3': { costUSD: 0.041, calls: 1, tokens: { input: 1000, output: 200, reasoning: 800 } },
|
|
174
|
+
// 'claude-sonnet-4-6': { costUSD: 0.025, calls: 2, tokens: { input: 4000, output: 1000, reasoning: 0 } }
|
|
153
175
|
// },
|
|
154
176
|
// bySession: { 'session_abc': { costUSD: 0.045, calls: 4 } },
|
|
155
177
|
// byUser: { 'user_123': { costUSD: 0.087, calls: 7 } },
|
|
178
|
+
// byFeature: { 'chat': { costUSD: 0.062, calls: 5 }, 'rag': { costUSD: 0.025, calls: 3 } },
|
|
156
179
|
// period: { from: '2026-04-16T10:00:00Z', to: '2026-04-16T11:00:00Z' }
|
|
157
180
|
// }
|
|
158
181
|
|
|
@@ -195,7 +218,7 @@ Prices are in **USD per 1 million tokens**.
|
|
|
195
218
|
}
|
|
196
219
|
```
|
|
197
220
|
|
|
198
|
-
Prices are updated every day via a GitHub Action that pulls from the [LiteLLM community model registry](https://github.com/BerriAI/litellm).
|
|
221
|
+
Prices are updated every day via a GitHub Action that pulls from the [LiteLLM community model registry](https://github.com/BerriAI/litellm) and commits the updated `prices.json` to the repo. Users with `syncPrices: true` (the default) always receive fresh prices at runtime — no `npm update` needed.
|
|
199
222
|
|
|
200
223
|
---
|
|
201
224
|
|
|
@@ -333,6 +356,10 @@ npx tokenwatch help # show help
|
|
|
333
356
|
|
|
334
357
|
By user:
|
|
335
358
|
user_123 $0.004231 (11 calls)
|
|
359
|
+
|
|
360
|
+
By feature:
|
|
361
|
+
chat $0.002500 (5 calls)
|
|
362
|
+
rag $0.001731 (6 calls)
|
|
336
363
|
───────────────────────────────────────────────────
|
|
337
364
|
```
|
|
338
365
|
|
|
@@ -349,9 +376,34 @@ Requires `storage: 'sqlite'` in your app and `better-sqlite3` installed.
|
|
|
349
376
|
|
|
350
377
|
---
|
|
351
378
|
|
|
379
|
+
## TypeScript
|
|
380
|
+
|
|
381
|
+
`__sessionId`, `__userId`, and `__feature` are typed via the `TrackingMeta` interface, which is automatically merged into the `create` params type by the wrapper. In most setups they just work with no cast required.
|
|
382
|
+
|
|
383
|
+
If you hit a type error (e.g. with stricter SDK versions), use `as Record<string, unknown>`:
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
await openai.chat.completions.create({
|
|
387
|
+
model: 'gpt-4o',
|
|
388
|
+
messages: [],
|
|
389
|
+
__sessionId: 'sess-1',
|
|
390
|
+
__feature: 'chat',
|
|
391
|
+
} as Record<string, unknown>)
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
`TrackingMeta` is exported if you need to annotate your own helper types:
|
|
395
|
+
|
|
396
|
+
```ts
|
|
397
|
+
import type { TrackingMeta } from '@diogonzafe/tokenwatch'
|
|
398
|
+
|
|
399
|
+
type MyParams = { model: string; messages: Message[] } & TrackingMeta
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
352
404
|
## Behaviour Guarantees
|
|
353
405
|
|
|
354
|
-
- `__sessionId` and `
|
|
406
|
+
- `__sessionId`, `__userId`, and `__feature` are **stripped before** the request reaches the API
|
|
355
407
|
- The response object returned is **identical** to the original SDK response
|
|
356
408
|
- `track()` is **synchronous and non-blocking** — zero latency added to API calls
|
|
357
409
|
- If the API call **fails**, no cost is recorded and the original error is re-thrown unchanged
|
package/dist/adapters.cjs
CHANGED
|
@@ -32,33 +32,46 @@ var PostgresStorage = class {
|
|
|
32
32
|
this.client = client;
|
|
33
33
|
}
|
|
34
34
|
client;
|
|
35
|
-
/** Creates the `tokenwatch_usage` table if it does not already exist.
|
|
35
|
+
/** Creates the `tokenwatch_usage` table if it does not already exist.
|
|
36
|
+
* Also adds new columns for databases created before v0.2.0. */
|
|
36
37
|
async migrate() {
|
|
37
38
|
await this.client.query(`
|
|
38
39
|
CREATE TABLE IF NOT EXISTS tokenwatch_usage (
|
|
39
|
-
id
|
|
40
|
-
model
|
|
41
|
-
input_tokens
|
|
42
|
-
output_tokens
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
id BIGSERIAL PRIMARY KEY,
|
|
41
|
+
model TEXT NOT NULL,
|
|
42
|
+
input_tokens INTEGER NOT NULL,
|
|
43
|
+
output_tokens INTEGER NOT NULL,
|
|
44
|
+
reasoning_tokens INTEGER NOT NULL DEFAULT 0,
|
|
45
|
+
cost_usd NUMERIC NOT NULL,
|
|
46
|
+
session_id TEXT,
|
|
47
|
+
user_id TEXT,
|
|
48
|
+
feature TEXT,
|
|
49
|
+
timestamp TIMESTAMPTZ NOT NULL
|
|
47
50
|
)
|
|
48
51
|
`);
|
|
52
|
+
await this.client.query(`
|
|
53
|
+
ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS reasoning_tokens INTEGER NOT NULL DEFAULT 0
|
|
54
|
+
`).catch(() => {
|
|
55
|
+
});
|
|
56
|
+
await this.client.query(`
|
|
57
|
+
ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS feature TEXT
|
|
58
|
+
`).catch(() => {
|
|
59
|
+
});
|
|
49
60
|
}
|
|
50
61
|
record(entry) {
|
|
51
62
|
this.client.query(
|
|
52
63
|
`INSERT INTO tokenwatch_usage
|
|
53
|
-
(model, input_tokens, output_tokens, cost_usd, session_id, user_id, timestamp)
|
|
54
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
64
|
+
(model, input_tokens, output_tokens, reasoning_tokens, cost_usd, session_id, user_id, feature, timestamp)
|
|
65
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
|
55
66
|
[
|
|
56
67
|
entry.model,
|
|
57
68
|
entry.inputTokens,
|
|
58
69
|
entry.outputTokens,
|
|
70
|
+
entry.reasoningTokens ?? 0,
|
|
59
71
|
entry.costUSD,
|
|
60
72
|
entry.sessionId ?? null,
|
|
61
73
|
entry.userId ?? null,
|
|
74
|
+
entry.feature ?? null,
|
|
62
75
|
entry.timestamp
|
|
63
76
|
]
|
|
64
77
|
).catch((err) => {
|
|
@@ -82,13 +95,16 @@ var PostgresStorage = class {
|
|
|
82
95
|
}
|
|
83
96
|
};
|
|
84
97
|
function rowToEntry(r) {
|
|
98
|
+
const reasoningTokens = r["reasoning_tokens"] ?? 0;
|
|
85
99
|
return {
|
|
86
100
|
model: r["model"],
|
|
87
101
|
inputTokens: r["input_tokens"],
|
|
88
102
|
outputTokens: r["output_tokens"],
|
|
103
|
+
...reasoningTokens > 0 && { reasoningTokens },
|
|
89
104
|
costUSD: Number(r["cost_usd"]),
|
|
90
105
|
...r["session_id"] != null && { sessionId: r["session_id"] },
|
|
91
106
|
...r["user_id"] != null && { userId: r["user_id"] },
|
|
107
|
+
...r["feature"] != null && { feature: r["feature"] },
|
|
92
108
|
timestamp: r["timestamp"] instanceof Date ? r["timestamp"].toISOString() : r["timestamp"]
|
|
93
109
|
};
|
|
94
110
|
}
|
|
@@ -99,33 +115,44 @@ var MySQLStorage = class {
|
|
|
99
115
|
this.client = client;
|
|
100
116
|
}
|
|
101
117
|
client;
|
|
102
|
-
/** Creates the `tokenwatch_usage` table if it does not already exist.
|
|
118
|
+
/** Creates the `tokenwatch_usage` table if it does not already exist.
|
|
119
|
+
* Also adds new columns for databases created before v0.2.0. */
|
|
103
120
|
async migrate() {
|
|
104
121
|
await this.client.execute(`
|
|
105
122
|
CREATE TABLE IF NOT EXISTS tokenwatch_usage (
|
|
106
|
-
id
|
|
107
|
-
model
|
|
108
|
-
input_tokens
|
|
109
|
-
output_tokens
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
123
|
+
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
124
|
+
model VARCHAR(255) NOT NULL,
|
|
125
|
+
input_tokens INT NOT NULL,
|
|
126
|
+
output_tokens INT NOT NULL,
|
|
127
|
+
reasoning_tokens INT NOT NULL DEFAULT 0,
|
|
128
|
+
cost_usd DECIMAL(18,8) NOT NULL,
|
|
129
|
+
session_id VARCHAR(255),
|
|
130
|
+
user_id VARCHAR(255),
|
|
131
|
+
feature VARCHAR(255),
|
|
132
|
+
timestamp DATETIME(3) NOT NULL
|
|
114
133
|
)
|
|
115
134
|
`);
|
|
135
|
+
await this.client.execute(`
|
|
136
|
+
ALTER TABLE tokenwatch_usage
|
|
137
|
+
ADD COLUMN IF NOT EXISTS reasoning_tokens INT NOT NULL DEFAULT 0,
|
|
138
|
+
ADD COLUMN IF NOT EXISTS feature VARCHAR(255)
|
|
139
|
+
`).catch(() => {
|
|
140
|
+
});
|
|
116
141
|
}
|
|
117
142
|
record(entry) {
|
|
118
143
|
this.client.execute(
|
|
119
144
|
`INSERT INTO tokenwatch_usage
|
|
120
|
-
(model, input_tokens, output_tokens, cost_usd, session_id, user_id, timestamp)
|
|
121
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
145
|
+
(model, input_tokens, output_tokens, reasoning_tokens, cost_usd, session_id, user_id, feature, timestamp)
|
|
146
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
122
147
|
[
|
|
123
148
|
entry.model,
|
|
124
149
|
entry.inputTokens,
|
|
125
150
|
entry.outputTokens,
|
|
151
|
+
entry.reasoningTokens ?? 0,
|
|
126
152
|
entry.costUSD,
|
|
127
153
|
entry.sessionId ?? null,
|
|
128
154
|
entry.userId ?? null,
|
|
155
|
+
entry.feature ?? null,
|
|
129
156
|
entry.timestamp
|
|
130
157
|
]
|
|
131
158
|
).catch((err) => {
|
|
@@ -149,13 +176,16 @@ var MySQLStorage = class {
|
|
|
149
176
|
}
|
|
150
177
|
};
|
|
151
178
|
function rowToEntry2(r) {
|
|
179
|
+
const reasoningTokens = r["reasoning_tokens"] ?? 0;
|
|
152
180
|
return {
|
|
153
181
|
model: r["model"],
|
|
154
182
|
inputTokens: r["input_tokens"],
|
|
155
183
|
outputTokens: r["output_tokens"],
|
|
184
|
+
...reasoningTokens > 0 && { reasoningTokens },
|
|
156
185
|
costUSD: Number(r["cost_usd"]),
|
|
157
186
|
...r["session_id"] != null && { sessionId: r["session_id"] },
|
|
158
187
|
...r["user_id"] != null && { userId: r["user_id"] },
|
|
188
|
+
...r["feature"] != null && { feature: r["feature"] },
|
|
159
189
|
timestamp: r["timestamp"] instanceof Date ? r["timestamp"].toISOString() : r["timestamp"]
|
|
160
190
|
};
|
|
161
191
|
}
|
|
@@ -179,9 +209,11 @@ var MongoStorage = class {
|
|
|
179
209
|
model: entry.model,
|
|
180
210
|
inputTokens: entry.inputTokens,
|
|
181
211
|
outputTokens: entry.outputTokens,
|
|
212
|
+
...entry.reasoningTokens !== void 0 && { reasoningTokens: entry.reasoningTokens },
|
|
182
213
|
costUSD: entry.costUSD,
|
|
183
214
|
sessionId: entry.sessionId ?? null,
|
|
184
215
|
userId: entry.userId ?? null,
|
|
216
|
+
...entry.feature !== void 0 && { feature: entry.feature },
|
|
185
217
|
timestamp: entry.timestamp
|
|
186
218
|
}).catch((err) => {
|
|
187
219
|
console.warn("[tokenwatch] MongoStorage.record failed:", err);
|
|
@@ -203,9 +235,11 @@ function docToEntry(doc) {
|
|
|
203
235
|
model: doc.model,
|
|
204
236
|
inputTokens: doc.inputTokens,
|
|
205
237
|
outputTokens: doc.outputTokens,
|
|
238
|
+
...doc.reasoningTokens != null && doc.reasoningTokens > 0 && { reasoningTokens: doc.reasoningTokens },
|
|
206
239
|
costUSD: doc.costUSD,
|
|
207
240
|
...doc.sessionId != null && { sessionId: doc.sessionId },
|
|
208
241
|
...doc.userId != null && { userId: doc.userId },
|
|
242
|
+
...doc.feature != null && { feature: doc.feature },
|
|
209
243
|
timestamp: doc.timestamp
|
|
210
244
|
};
|
|
211
245
|
}
|
package/dist/adapters.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapters/index.ts","../src/adapters/postgres.ts","../src/adapters/mysql.ts","../src/adapters/mongodb.ts"],"sourcesContent":["export { PostgresStorage } from './postgres.js'\nexport { MySQLStorage } from './mysql.js'\nexport { MongoStorage } from './mongodb.js'\n","import type { IStorage, UsageEntry } from '../types/index.js'\n\n/**\n * IStorage adapter for PostgreSQL using the `pg` driver.\n *\n * Install peer dep: npm install pg\n * Types (optional): npm install -D @types/pg\n *\n * @example\n * ```ts\n * import { Pool } from 'pg'\n * import { createTracker } from '@diogonzafe/tokenwatch'\n * import { PostgresStorage } from '@diogonzafe/tokenwatch/adapters'\n *\n * const pool = new Pool({ connectionString: process.env.DATABASE_URL })\n * const storage = new PostgresStorage(pool)\n * await storage.migrate() // create table if it doesn't exist\n *\n * const tracker = createTracker({ storage })\n * ```\n */\n\n// Minimal structural types so the adapter compiles without `pg` installed\ninterface QueryClient {\n query(sql: string, values?: unknown[]): Promise<{ rows: unknown[] }>\n}\n\nexport class PostgresStorage implements IStorage {\n constructor(private readonly client: QueryClient) {}\n\n /** Creates the `tokenwatch_usage` table if it does not already exist. */\n async migrate(): Promise<void> {\n await this.client.query(`\n CREATE TABLE IF NOT EXISTS tokenwatch_usage (\n id BIGSERIAL PRIMARY KEY,\n model TEXT NOT NULL,\n input_tokens INTEGER NOT NULL,\n output_tokens INTEGER NOT NULL,\n cost_usd NUMERIC NOT NULL,\n session_id TEXT,\n user_id TEXT,\n timestamp TIMESTAMPTZ NOT NULL\n )\n `)\n }\n\n record(entry: UsageEntry): void {\n this.client\n .query(\n `INSERT INTO tokenwatch_usage\n (model, input_tokens, output_tokens, cost_usd, session_id, user_id, timestamp)\n VALUES ($1, $2, $3, $4, $5, $6, $7)`,\n [\n entry.model,\n entry.inputTokens,\n entry.outputTokens,\n entry.costUSD,\n entry.sessionId ?? null,\n entry.userId ?? null,\n entry.timestamp,\n ],\n )\n .catch((err: unknown) => {\n console.warn('[tokenwatch] PostgresStorage.record failed:', err)\n })\n }\n\n async getAll(): Promise<UsageEntry[]> {\n const result = await this.client.query(\n 'SELECT * FROM tokenwatch_usage ORDER BY timestamp ASC',\n )\n return (result.rows as Array<Record<string, unknown>>).map(rowToEntry)\n }\n\n async clearAll(): Promise<void> {\n await this.client.query('DELETE FROM tokenwatch_usage')\n }\n\n async clearSession(sessionId: string): Promise<void> {\n await this.client.query(\n 'DELETE FROM tokenwatch_usage WHERE session_id = $1',\n [sessionId],\n )\n }\n}\n\nfunction rowToEntry(r: Record<string, unknown>): UsageEntry {\n return {\n model: r['model'] as string,\n inputTokens: r['input_tokens'] as number,\n outputTokens: r['output_tokens'] as number,\n costUSD: Number(r['cost_usd']),\n ...(r['session_id'] != null && { sessionId: r['session_id'] as string }),\n ...(r['user_id'] != null && { userId: r['user_id'] as string }),\n timestamp:\n r['timestamp'] instanceof Date\n ? (r['timestamp'] as Date).toISOString()\n : (r['timestamp'] as string),\n }\n}\n","import type { IStorage, UsageEntry } from '../types/index.js'\n\n/**\n * IStorage adapter for MySQL / MariaDB using the `mysql2` driver.\n *\n * Install peer dep: npm install mysql2\n *\n * @example\n * ```ts\n * import mysql from 'mysql2/promise'\n * import { createTracker } from '@diogonzafe/tokenwatch'\n * import { MySQLStorage } from '@diogonzafe/tokenwatch/adapters'\n *\n * const pool = mysql.createPool({ uri: process.env.MYSQL_URL })\n * const storage = new MySQLStorage(pool)\n * await storage.migrate() // create table if it doesn't exist\n *\n * const tracker = createTracker({ storage })\n * ```\n */\n\n// Minimal structural type so the adapter compiles without `mysql2` installed\ninterface QueryClient {\n execute(sql: string, values?: unknown[]): Promise<[unknown]>\n}\n\nexport class MySQLStorage implements IStorage {\n constructor(private readonly client: QueryClient) {}\n\n /** Creates the `tokenwatch_usage` table if it does not already exist. */\n async migrate(): Promise<void> {\n await this.client.execute(`\n CREATE TABLE IF NOT EXISTS tokenwatch_usage (\n id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n model VARCHAR(255) NOT NULL,\n input_tokens INT NOT NULL,\n output_tokens INT NOT NULL,\n cost_usd DECIMAL(18,8) NOT NULL,\n session_id VARCHAR(255),\n user_id VARCHAR(255),\n timestamp DATETIME(3) NOT NULL\n )\n `)\n }\n\n record(entry: UsageEntry): void {\n this.client\n .execute(\n `INSERT INTO tokenwatch_usage\n (model, input_tokens, output_tokens, cost_usd, session_id, user_id, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n [\n entry.model,\n entry.inputTokens,\n entry.outputTokens,\n entry.costUSD,\n entry.sessionId ?? null,\n entry.userId ?? null,\n entry.timestamp,\n ],\n )\n .catch((err: unknown) => {\n console.warn('[tokenwatch] MySQLStorage.record failed:', err)\n })\n }\n\n async getAll(): Promise<UsageEntry[]> {\n const [rows] = await this.client.execute(\n 'SELECT * FROM tokenwatch_usage ORDER BY timestamp ASC',\n )\n return (rows as Array<Record<string, unknown>>).map(rowToEntry)\n }\n\n async clearAll(): Promise<void> {\n await this.client.execute('DELETE FROM tokenwatch_usage')\n }\n\n async clearSession(sessionId: string): Promise<void> {\n await this.client.execute(\n 'DELETE FROM tokenwatch_usage WHERE session_id = ?',\n [sessionId],\n )\n }\n}\n\nfunction rowToEntry(r: Record<string, unknown>): UsageEntry {\n return {\n model: r['model'] as string,\n inputTokens: r['input_tokens'] as number,\n outputTokens: r['output_tokens'] as number,\n costUSD: Number(r['cost_usd']),\n ...(r['session_id'] != null && { sessionId: r['session_id'] as string }),\n ...(r['user_id'] != null && { userId: r['user_id'] as string }),\n timestamp:\n r['timestamp'] instanceof Date\n ? (r['timestamp'] as Date).toISOString()\n : (r['timestamp'] as string),\n }\n}\n","import type { IStorage, UsageEntry } from '../types/index.js'\n\n/**\n * IStorage adapter for MongoDB using the official `mongodb` driver.\n *\n * Install peer dep: npm install mongodb\n *\n * @example\n * ```ts\n * import { MongoClient } from 'mongodb'\n * import { createTracker } from '@diogonzafe/tokenwatch'\n * import { MongoStorage } from '@diogonzafe/tokenwatch/adapters'\n *\n * const client = new MongoClient(process.env.MONGO_URL!)\n * await client.connect()\n *\n * const storage = new MongoStorage(client.db('myapp'))\n * const tracker = createTracker({ storage })\n * ```\n *\n * Recommended index (run once at startup):\n * ```ts\n * await storage.createIndexes()\n * ```\n */\n\n// Minimal structural types so the adapter compiles without `mongodb` installed\ninterface MongoDocument {\n _id?: unknown\n model: string\n inputTokens: number\n outputTokens: number\n costUSD: number\n sessionId?: string | null\n userId?: string | null\n timestamp: string\n}\n\ninterface MongoCursor {\n sort(sort: Record<string, unknown>): MongoCursor\n toArray(): Promise<MongoDocument[]>\n}\n\ninterface Collection {\n insertOne(doc: MongoDocument): Promise<unknown>\n find(filter: Record<string, unknown>): MongoCursor\n deleteMany(filter: Record<string, unknown>): Promise<unknown>\n createIndex(index: Record<string, unknown>): Promise<unknown>\n}\n\ninterface Database {\n collection(name: string): Collection\n}\n\nconst COLLECTION = 'tokenwatch_usage'\n\nexport class MongoStorage implements IStorage {\n private readonly col: Collection\n\n constructor(db: Database) {\n this.col = db.collection(COLLECTION)\n }\n\n /** Creates recommended indexes for query performance. Call once at startup. */\n async createIndexes(): Promise<void> {\n await this.col.createIndex({ timestamp: 1 })\n await this.col.createIndex({ sessionId: 1 })\n await this.col.createIndex({ userId: 1 })\n await this.col.createIndex({ model: 1 })\n }\n\n record(entry: UsageEntry): void {\n this.col\n .insertOne({\n model: entry.model,\n inputTokens: entry.inputTokens,\n outputTokens: entry.outputTokens,\n costUSD: entry.costUSD,\n sessionId: entry.sessionId ?? null,\n userId: entry.userId ?? null,\n timestamp: entry.timestamp,\n })\n .catch((err: unknown) => {\n console.warn('[tokenwatch] MongoStorage.record failed:', err)\n })\n }\n\n async getAll(): Promise<UsageEntry[]> {\n const docs = await this.col.find({}).sort({ timestamp: 1 }).toArray()\n return docs.map(docToEntry)\n }\n\n async clearAll(): Promise<void> {\n await this.col.deleteMany({})\n }\n\n async clearSession(sessionId: string): Promise<void> {\n await this.col.deleteMany({ sessionId })\n }\n}\n\nfunction docToEntry(doc: MongoDocument): UsageEntry {\n return {\n model: doc.model,\n inputTokens: doc.inputTokens,\n outputTokens: doc.outputTokens,\n costUSD: doc.costUSD,\n ...(doc.sessionId != null && { sessionId: doc.sessionId }),\n ...(doc.userId != null && { userId: doc.userId }),\n timestamp: doc.timestamp,\n }\n}\n"],"mappings":"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","names":["rowToEntry"]}
|
|
1
|
+
{"version":3,"sources":["../src/adapters/index.ts","../src/adapters/postgres.ts","../src/adapters/mysql.ts","../src/adapters/mongodb.ts"],"sourcesContent":["export { PostgresStorage } from './postgres.js'\nexport { MySQLStorage } from './mysql.js'\nexport { MongoStorage } from './mongodb.js'\n","import type { IStorage, UsageEntry } from '../types/index.js'\n\n/**\n * IStorage adapter for PostgreSQL using the `pg` driver.\n *\n * Install peer dep: npm install pg\n * Types (optional): npm install -D @types/pg\n *\n * @example\n * ```ts\n * import { Pool } from 'pg'\n * import { createTracker } from '@diogonzafe/tokenwatch'\n * import { PostgresStorage } from '@diogonzafe/tokenwatch/adapters'\n *\n * const pool = new Pool({ connectionString: process.env.DATABASE_URL })\n * const storage = new PostgresStorage(pool)\n * await storage.migrate() // create table if it doesn't exist\n *\n * const tracker = createTracker({ storage })\n * ```\n */\n\n// Minimal structural types so the adapter compiles without `pg` installed\ninterface QueryClient {\n query(sql: string, values?: unknown[]): Promise<{ rows: unknown[] }>\n}\n\nexport class PostgresStorage implements IStorage {\n constructor(private readonly client: QueryClient) {}\n\n /** Creates the `tokenwatch_usage` table if it does not already exist.\n * Also adds new columns for databases created before v0.2.0. */\n async migrate(): Promise<void> {\n await this.client.query(`\n CREATE TABLE IF NOT EXISTS tokenwatch_usage (\n id BIGSERIAL PRIMARY KEY,\n model TEXT NOT NULL,\n input_tokens INTEGER NOT NULL,\n output_tokens INTEGER NOT NULL,\n reasoning_tokens INTEGER NOT NULL DEFAULT 0,\n cost_usd NUMERIC NOT NULL,\n session_id TEXT,\n user_id TEXT,\n feature TEXT,\n timestamp TIMESTAMPTZ NOT NULL\n )\n `)\n // Incremental migrations for databases created before v0.2.0\n await this.client.query(`\n ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS reasoning_tokens INTEGER NOT NULL DEFAULT 0\n `).catch(() => { /* column already exists in older Postgres versions that don't support IF NOT EXISTS */ })\n await this.client.query(`\n ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS feature TEXT\n `).catch(() => { /* same */ })\n }\n\n record(entry: UsageEntry): void {\n this.client\n .query(\n `INSERT INTO tokenwatch_usage\n (model, input_tokens, output_tokens, reasoning_tokens, cost_usd, session_id, user_id, feature, timestamp)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,\n [\n entry.model,\n entry.inputTokens,\n entry.outputTokens,\n entry.reasoningTokens ?? 0,\n entry.costUSD,\n entry.sessionId ?? null,\n entry.userId ?? null,\n entry.feature ?? null,\n entry.timestamp,\n ],\n )\n .catch((err: unknown) => {\n console.warn('[tokenwatch] PostgresStorage.record failed:', err)\n })\n }\n\n async getAll(): Promise<UsageEntry[]> {\n const result = await this.client.query(\n 'SELECT * FROM tokenwatch_usage ORDER BY timestamp ASC',\n )\n return (result.rows as Array<Record<string, unknown>>).map(rowToEntry)\n }\n\n async clearAll(): Promise<void> {\n await this.client.query('DELETE FROM tokenwatch_usage')\n }\n\n async clearSession(sessionId: string): Promise<void> {\n await this.client.query(\n 'DELETE FROM tokenwatch_usage WHERE session_id = $1',\n [sessionId],\n )\n }\n}\n\nfunction rowToEntry(r: Record<string, unknown>): UsageEntry {\n const reasoningTokens = (r['reasoning_tokens'] as number | null) ?? 0\n return {\n model: r['model'] as string,\n inputTokens: r['input_tokens'] as number,\n outputTokens: r['output_tokens'] as number,\n ...(reasoningTokens > 0 && { reasoningTokens }),\n costUSD: Number(r['cost_usd']),\n ...(r['session_id'] != null && { sessionId: r['session_id'] as string }),\n ...(r['user_id'] != null && { userId: r['user_id'] as string }),\n ...(r['feature'] != null && { feature: r['feature'] as string }),\n timestamp:\n r['timestamp'] instanceof Date\n ? (r['timestamp'] as Date).toISOString()\n : (r['timestamp'] as string),\n }\n}\n","import type { IStorage, UsageEntry } from '../types/index.js'\n\n/**\n * IStorage adapter for MySQL / MariaDB using the `mysql2` driver.\n *\n * Install peer dep: npm install mysql2\n *\n * @example\n * ```ts\n * import mysql from 'mysql2/promise'\n * import { createTracker } from '@diogonzafe/tokenwatch'\n * import { MySQLStorage } from '@diogonzafe/tokenwatch/adapters'\n *\n * const pool = mysql.createPool({ uri: process.env.MYSQL_URL })\n * const storage = new MySQLStorage(pool)\n * await storage.migrate() // create table if it doesn't exist\n *\n * const tracker = createTracker({ storage })\n * ```\n */\n\n// Minimal structural type so the adapter compiles without `mysql2` installed\ninterface QueryClient {\n execute(sql: string, values?: unknown[]): Promise<[unknown]>\n}\n\nexport class MySQLStorage implements IStorage {\n constructor(private readonly client: QueryClient) {}\n\n /** Creates the `tokenwatch_usage` table if it does not already exist.\n * Also adds new columns for databases created before v0.2.0. */\n async migrate(): Promise<void> {\n await this.client.execute(`\n CREATE TABLE IF NOT EXISTS tokenwatch_usage (\n id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n model VARCHAR(255) NOT NULL,\n input_tokens INT NOT NULL,\n output_tokens INT NOT NULL,\n reasoning_tokens INT NOT NULL DEFAULT 0,\n cost_usd DECIMAL(18,8) NOT NULL,\n session_id VARCHAR(255),\n user_id VARCHAR(255),\n feature VARCHAR(255),\n timestamp DATETIME(3) NOT NULL\n )\n `)\n // Incremental migrations for databases created before v0.2.0\n await this.client.execute(`\n ALTER TABLE tokenwatch_usage\n ADD COLUMN IF NOT EXISTS reasoning_tokens INT NOT NULL DEFAULT 0,\n ADD COLUMN IF NOT EXISTS feature VARCHAR(255)\n `).catch(() => { /* MySQL < 8.0 may not support IF NOT EXISTS — ignore if columns already exist */ })\n }\n\n record(entry: UsageEntry): void {\n this.client\n .execute(\n `INSERT INTO tokenwatch_usage\n (model, input_tokens, output_tokens, reasoning_tokens, cost_usd, session_id, user_id, feature, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n entry.model,\n entry.inputTokens,\n entry.outputTokens,\n entry.reasoningTokens ?? 0,\n entry.costUSD,\n entry.sessionId ?? null,\n entry.userId ?? null,\n entry.feature ?? null,\n entry.timestamp,\n ],\n )\n .catch((err: unknown) => {\n console.warn('[tokenwatch] MySQLStorage.record failed:', err)\n })\n }\n\n async getAll(): Promise<UsageEntry[]> {\n const [rows] = await this.client.execute(\n 'SELECT * FROM tokenwatch_usage ORDER BY timestamp ASC',\n )\n return (rows as Array<Record<string, unknown>>).map(rowToEntry)\n }\n\n async clearAll(): Promise<void> {\n await this.client.execute('DELETE FROM tokenwatch_usage')\n }\n\n async clearSession(sessionId: string): Promise<void> {\n await this.client.execute(\n 'DELETE FROM tokenwatch_usage WHERE session_id = ?',\n [sessionId],\n )\n }\n}\n\nfunction rowToEntry(r: Record<string, unknown>): UsageEntry {\n const reasoningTokens = (r['reasoning_tokens'] as number | null) ?? 0\n return {\n model: r['model'] as string,\n inputTokens: r['input_tokens'] as number,\n outputTokens: r['output_tokens'] as number,\n ...(reasoningTokens > 0 && { reasoningTokens }),\n costUSD: Number(r['cost_usd']),\n ...(r['session_id'] != null && { sessionId: r['session_id'] as string }),\n ...(r['user_id'] != null && { userId: r['user_id'] as string }),\n ...(r['feature'] != null && { feature: r['feature'] as string }),\n timestamp:\n r['timestamp'] instanceof Date\n ? (r['timestamp'] as Date).toISOString()\n : (r['timestamp'] as string),\n }\n}\n","import type { IStorage, UsageEntry } from '../types/index.js'\n\n/**\n * IStorage adapter for MongoDB using the official `mongodb` driver.\n *\n * Install peer dep: npm install mongodb\n *\n * @example\n * ```ts\n * import { MongoClient } from 'mongodb'\n * import { createTracker } from '@diogonzafe/tokenwatch'\n * import { MongoStorage } from '@diogonzafe/tokenwatch/adapters'\n *\n * const client = new MongoClient(process.env.MONGO_URL!)\n * await client.connect()\n *\n * const storage = new MongoStorage(client.db('myapp'))\n * const tracker = createTracker({ storage })\n * ```\n *\n * Recommended index (run once at startup):\n * ```ts\n * await storage.createIndexes()\n * ```\n */\n\n// Minimal structural types so the adapter compiles without `mongodb` installed\ninterface MongoDocument {\n _id?: unknown\n model: string\n inputTokens: number\n outputTokens: number\n reasoningTokens?: number\n costUSD: number\n sessionId?: string | null\n userId?: string | null\n feature?: string | null\n timestamp: string\n}\n\ninterface MongoCursor {\n sort(sort: Record<string, unknown>): MongoCursor\n toArray(): Promise<MongoDocument[]>\n}\n\ninterface Collection {\n insertOne(doc: MongoDocument): Promise<unknown>\n find(filter: Record<string, unknown>): MongoCursor\n deleteMany(filter: Record<string, unknown>): Promise<unknown>\n createIndex(index: Record<string, unknown>): Promise<unknown>\n}\n\ninterface Database {\n collection(name: string): Collection\n}\n\nconst COLLECTION = 'tokenwatch_usage'\n\nexport class MongoStorage implements IStorage {\n private readonly col: Collection\n\n constructor(db: Database) {\n this.col = db.collection(COLLECTION)\n }\n\n /** Creates recommended indexes for query performance. Call once at startup. */\n async createIndexes(): Promise<void> {\n await this.col.createIndex({ timestamp: 1 })\n await this.col.createIndex({ sessionId: 1 })\n await this.col.createIndex({ userId: 1 })\n await this.col.createIndex({ model: 1 })\n }\n\n record(entry: UsageEntry): void {\n this.col\n .insertOne({\n model: entry.model,\n inputTokens: entry.inputTokens,\n outputTokens: entry.outputTokens,\n ...(entry.reasoningTokens !== undefined && { reasoningTokens: entry.reasoningTokens }),\n costUSD: entry.costUSD,\n sessionId: entry.sessionId ?? null,\n userId: entry.userId ?? null,\n ...(entry.feature !== undefined && { feature: entry.feature }),\n timestamp: entry.timestamp,\n })\n .catch((err: unknown) => {\n console.warn('[tokenwatch] MongoStorage.record failed:', err)\n })\n }\n\n async getAll(): Promise<UsageEntry[]> {\n const docs = await this.col.find({}).sort({ timestamp: 1 }).toArray()\n return docs.map(docToEntry)\n }\n\n async clearAll(): Promise<void> {\n await this.col.deleteMany({})\n }\n\n async clearSession(sessionId: string): Promise<void> {\n await this.col.deleteMany({ sessionId })\n }\n}\n\nfunction docToEntry(doc: MongoDocument): UsageEntry {\n return {\n model: doc.model,\n inputTokens: doc.inputTokens,\n outputTokens: doc.outputTokens,\n ...(doc.reasoningTokens != null && doc.reasoningTokens > 0 && { reasoningTokens: doc.reasoningTokens }),\n costUSD: doc.costUSD,\n ...(doc.sessionId != null && { sessionId: doc.sessionId }),\n ...(doc.userId != null && { userId: doc.userId }),\n ...(doc.feature != null && { feature: doc.feature }),\n timestamp: doc.timestamp,\n }\n}\n"],"mappings":"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","names":["rowToEntry"]}
|
package/dist/adapters.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { I as IStorage, U as UsageEntry } from './index-
|
|
1
|
+
import { I as IStorage, U as UsageEntry } from './index-B_EmA3K7.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* IStorage adapter for PostgreSQL using the `pg` driver.
|
|
@@ -27,7 +27,8 @@ interface QueryClient$1 {
|
|
|
27
27
|
declare class PostgresStorage implements IStorage {
|
|
28
28
|
private readonly client;
|
|
29
29
|
constructor(client: QueryClient$1);
|
|
30
|
-
/** Creates the `tokenwatch_usage` table if it does not already exist.
|
|
30
|
+
/** Creates the `tokenwatch_usage` table if it does not already exist.
|
|
31
|
+
* Also adds new columns for databases created before v0.2.0. */
|
|
31
32
|
migrate(): Promise<void>;
|
|
32
33
|
record(entry: UsageEntry): void;
|
|
33
34
|
getAll(): Promise<UsageEntry[]>;
|
|
@@ -59,7 +60,8 @@ interface QueryClient {
|
|
|
59
60
|
declare class MySQLStorage implements IStorage {
|
|
60
61
|
private readonly client;
|
|
61
62
|
constructor(client: QueryClient);
|
|
62
|
-
/** Creates the `tokenwatch_usage` table if it does not already exist.
|
|
63
|
+
/** Creates the `tokenwatch_usage` table if it does not already exist.
|
|
64
|
+
* Also adds new columns for databases created before v0.2.0. */
|
|
63
65
|
migrate(): Promise<void>;
|
|
64
66
|
record(entry: UsageEntry): void;
|
|
65
67
|
getAll(): Promise<UsageEntry[]>;
|
|
@@ -95,9 +97,11 @@ interface MongoDocument {
|
|
|
95
97
|
model: string;
|
|
96
98
|
inputTokens: number;
|
|
97
99
|
outputTokens: number;
|
|
100
|
+
reasoningTokens?: number;
|
|
98
101
|
costUSD: number;
|
|
99
102
|
sessionId?: string | null;
|
|
100
103
|
userId?: string | null;
|
|
104
|
+
feature?: string | null;
|
|
101
105
|
timestamp: string;
|
|
102
106
|
}
|
|
103
107
|
interface MongoCursor {
|
package/dist/adapters.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { I as IStorage, U as UsageEntry } from './index-
|
|
1
|
+
import { I as IStorage, U as UsageEntry } from './index-B_EmA3K7.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* IStorage adapter for PostgreSQL using the `pg` driver.
|
|
@@ -27,7 +27,8 @@ interface QueryClient$1 {
|
|
|
27
27
|
declare class PostgresStorage implements IStorage {
|
|
28
28
|
private readonly client;
|
|
29
29
|
constructor(client: QueryClient$1);
|
|
30
|
-
/** Creates the `tokenwatch_usage` table if it does not already exist.
|
|
30
|
+
/** Creates the `tokenwatch_usage` table if it does not already exist.
|
|
31
|
+
* Also adds new columns for databases created before v0.2.0. */
|
|
31
32
|
migrate(): Promise<void>;
|
|
32
33
|
record(entry: UsageEntry): void;
|
|
33
34
|
getAll(): Promise<UsageEntry[]>;
|
|
@@ -59,7 +60,8 @@ interface QueryClient {
|
|
|
59
60
|
declare class MySQLStorage implements IStorage {
|
|
60
61
|
private readonly client;
|
|
61
62
|
constructor(client: QueryClient);
|
|
62
|
-
/** Creates the `tokenwatch_usage` table if it does not already exist.
|
|
63
|
+
/** Creates the `tokenwatch_usage` table if it does not already exist.
|
|
64
|
+
* Also adds new columns for databases created before v0.2.0. */
|
|
63
65
|
migrate(): Promise<void>;
|
|
64
66
|
record(entry: UsageEntry): void;
|
|
65
67
|
getAll(): Promise<UsageEntry[]>;
|
|
@@ -95,9 +97,11 @@ interface MongoDocument {
|
|
|
95
97
|
model: string;
|
|
96
98
|
inputTokens: number;
|
|
97
99
|
outputTokens: number;
|
|
100
|
+
reasoningTokens?: number;
|
|
98
101
|
costUSD: number;
|
|
99
102
|
sessionId?: string | null;
|
|
100
103
|
userId?: string | null;
|
|
104
|
+
feature?: string | null;
|
|
101
105
|
timestamp: string;
|
|
102
106
|
}
|
|
103
107
|
interface MongoCursor {
|
package/dist/adapters.js
CHANGED
|
@@ -4,33 +4,46 @@ var PostgresStorage = class {
|
|
|
4
4
|
this.client = client;
|
|
5
5
|
}
|
|
6
6
|
client;
|
|
7
|
-
/** Creates the `tokenwatch_usage` table if it does not already exist.
|
|
7
|
+
/** Creates the `tokenwatch_usage` table if it does not already exist.
|
|
8
|
+
* Also adds new columns for databases created before v0.2.0. */
|
|
8
9
|
async migrate() {
|
|
9
10
|
await this.client.query(`
|
|
10
11
|
CREATE TABLE IF NOT EXISTS tokenwatch_usage (
|
|
11
|
-
id
|
|
12
|
-
model
|
|
13
|
-
input_tokens
|
|
14
|
-
output_tokens
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
id BIGSERIAL PRIMARY KEY,
|
|
13
|
+
model TEXT NOT NULL,
|
|
14
|
+
input_tokens INTEGER NOT NULL,
|
|
15
|
+
output_tokens INTEGER NOT NULL,
|
|
16
|
+
reasoning_tokens INTEGER NOT NULL DEFAULT 0,
|
|
17
|
+
cost_usd NUMERIC NOT NULL,
|
|
18
|
+
session_id TEXT,
|
|
19
|
+
user_id TEXT,
|
|
20
|
+
feature TEXT,
|
|
21
|
+
timestamp TIMESTAMPTZ NOT NULL
|
|
19
22
|
)
|
|
20
23
|
`);
|
|
24
|
+
await this.client.query(`
|
|
25
|
+
ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS reasoning_tokens INTEGER NOT NULL DEFAULT 0
|
|
26
|
+
`).catch(() => {
|
|
27
|
+
});
|
|
28
|
+
await this.client.query(`
|
|
29
|
+
ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS feature TEXT
|
|
30
|
+
`).catch(() => {
|
|
31
|
+
});
|
|
21
32
|
}
|
|
22
33
|
record(entry) {
|
|
23
34
|
this.client.query(
|
|
24
35
|
`INSERT INTO tokenwatch_usage
|
|
25
|
-
(model, input_tokens, output_tokens, cost_usd, session_id, user_id, timestamp)
|
|
26
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
36
|
+
(model, input_tokens, output_tokens, reasoning_tokens, cost_usd, session_id, user_id, feature, timestamp)
|
|
37
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
|
27
38
|
[
|
|
28
39
|
entry.model,
|
|
29
40
|
entry.inputTokens,
|
|
30
41
|
entry.outputTokens,
|
|
42
|
+
entry.reasoningTokens ?? 0,
|
|
31
43
|
entry.costUSD,
|
|
32
44
|
entry.sessionId ?? null,
|
|
33
45
|
entry.userId ?? null,
|
|
46
|
+
entry.feature ?? null,
|
|
34
47
|
entry.timestamp
|
|
35
48
|
]
|
|
36
49
|
).catch((err) => {
|
|
@@ -54,13 +67,16 @@ var PostgresStorage = class {
|
|
|
54
67
|
}
|
|
55
68
|
};
|
|
56
69
|
function rowToEntry(r) {
|
|
70
|
+
const reasoningTokens = r["reasoning_tokens"] ?? 0;
|
|
57
71
|
return {
|
|
58
72
|
model: r["model"],
|
|
59
73
|
inputTokens: r["input_tokens"],
|
|
60
74
|
outputTokens: r["output_tokens"],
|
|
75
|
+
...reasoningTokens > 0 && { reasoningTokens },
|
|
61
76
|
costUSD: Number(r["cost_usd"]),
|
|
62
77
|
...r["session_id"] != null && { sessionId: r["session_id"] },
|
|
63
78
|
...r["user_id"] != null && { userId: r["user_id"] },
|
|
79
|
+
...r["feature"] != null && { feature: r["feature"] },
|
|
64
80
|
timestamp: r["timestamp"] instanceof Date ? r["timestamp"].toISOString() : r["timestamp"]
|
|
65
81
|
};
|
|
66
82
|
}
|
|
@@ -71,33 +87,44 @@ var MySQLStorage = class {
|
|
|
71
87
|
this.client = client;
|
|
72
88
|
}
|
|
73
89
|
client;
|
|
74
|
-
/** Creates the `tokenwatch_usage` table if it does not already exist.
|
|
90
|
+
/** Creates the `tokenwatch_usage` table if it does not already exist.
|
|
91
|
+
* Also adds new columns for databases created before v0.2.0. */
|
|
75
92
|
async migrate() {
|
|
76
93
|
await this.client.execute(`
|
|
77
94
|
CREATE TABLE IF NOT EXISTS tokenwatch_usage (
|
|
78
|
-
id
|
|
79
|
-
model
|
|
80
|
-
input_tokens
|
|
81
|
-
output_tokens
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
95
|
+
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
96
|
+
model VARCHAR(255) NOT NULL,
|
|
97
|
+
input_tokens INT NOT NULL,
|
|
98
|
+
output_tokens INT NOT NULL,
|
|
99
|
+
reasoning_tokens INT NOT NULL DEFAULT 0,
|
|
100
|
+
cost_usd DECIMAL(18,8) NOT NULL,
|
|
101
|
+
session_id VARCHAR(255),
|
|
102
|
+
user_id VARCHAR(255),
|
|
103
|
+
feature VARCHAR(255),
|
|
104
|
+
timestamp DATETIME(3) NOT NULL
|
|
86
105
|
)
|
|
87
106
|
`);
|
|
107
|
+
await this.client.execute(`
|
|
108
|
+
ALTER TABLE tokenwatch_usage
|
|
109
|
+
ADD COLUMN IF NOT EXISTS reasoning_tokens INT NOT NULL DEFAULT 0,
|
|
110
|
+
ADD COLUMN IF NOT EXISTS feature VARCHAR(255)
|
|
111
|
+
`).catch(() => {
|
|
112
|
+
});
|
|
88
113
|
}
|
|
89
114
|
record(entry) {
|
|
90
115
|
this.client.execute(
|
|
91
116
|
`INSERT INTO tokenwatch_usage
|
|
92
|
-
(model, input_tokens, output_tokens, cost_usd, session_id, user_id, timestamp)
|
|
93
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
117
|
+
(model, input_tokens, output_tokens, reasoning_tokens, cost_usd, session_id, user_id, feature, timestamp)
|
|
118
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
94
119
|
[
|
|
95
120
|
entry.model,
|
|
96
121
|
entry.inputTokens,
|
|
97
122
|
entry.outputTokens,
|
|
123
|
+
entry.reasoningTokens ?? 0,
|
|
98
124
|
entry.costUSD,
|
|
99
125
|
entry.sessionId ?? null,
|
|
100
126
|
entry.userId ?? null,
|
|
127
|
+
entry.feature ?? null,
|
|
101
128
|
entry.timestamp
|
|
102
129
|
]
|
|
103
130
|
).catch((err) => {
|
|
@@ -121,13 +148,16 @@ var MySQLStorage = class {
|
|
|
121
148
|
}
|
|
122
149
|
};
|
|
123
150
|
function rowToEntry2(r) {
|
|
151
|
+
const reasoningTokens = r["reasoning_tokens"] ?? 0;
|
|
124
152
|
return {
|
|
125
153
|
model: r["model"],
|
|
126
154
|
inputTokens: r["input_tokens"],
|
|
127
155
|
outputTokens: r["output_tokens"],
|
|
156
|
+
...reasoningTokens > 0 && { reasoningTokens },
|
|
128
157
|
costUSD: Number(r["cost_usd"]),
|
|
129
158
|
...r["session_id"] != null && { sessionId: r["session_id"] },
|
|
130
159
|
...r["user_id"] != null && { userId: r["user_id"] },
|
|
160
|
+
...r["feature"] != null && { feature: r["feature"] },
|
|
131
161
|
timestamp: r["timestamp"] instanceof Date ? r["timestamp"].toISOString() : r["timestamp"]
|
|
132
162
|
};
|
|
133
163
|
}
|
|
@@ -151,9 +181,11 @@ var MongoStorage = class {
|
|
|
151
181
|
model: entry.model,
|
|
152
182
|
inputTokens: entry.inputTokens,
|
|
153
183
|
outputTokens: entry.outputTokens,
|
|
184
|
+
...entry.reasoningTokens !== void 0 && { reasoningTokens: entry.reasoningTokens },
|
|
154
185
|
costUSD: entry.costUSD,
|
|
155
186
|
sessionId: entry.sessionId ?? null,
|
|
156
187
|
userId: entry.userId ?? null,
|
|
188
|
+
...entry.feature !== void 0 && { feature: entry.feature },
|
|
157
189
|
timestamp: entry.timestamp
|
|
158
190
|
}).catch((err) => {
|
|
159
191
|
console.warn("[tokenwatch] MongoStorage.record failed:", err);
|
|
@@ -175,9 +207,11 @@ function docToEntry(doc) {
|
|
|
175
207
|
model: doc.model,
|
|
176
208
|
inputTokens: doc.inputTokens,
|
|
177
209
|
outputTokens: doc.outputTokens,
|
|
210
|
+
...doc.reasoningTokens != null && doc.reasoningTokens > 0 && { reasoningTokens: doc.reasoningTokens },
|
|
178
211
|
costUSD: doc.costUSD,
|
|
179
212
|
...doc.sessionId != null && { sessionId: doc.sessionId },
|
|
180
213
|
...doc.userId != null && { userId: doc.userId },
|
|
214
|
+
...doc.feature != null && { feature: doc.feature },
|
|
181
215
|
timestamp: doc.timestamp
|
|
182
216
|
};
|
|
183
217
|
}
|