@diogonzafe/tokenwatch 0.1.0 → 0.1.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/dist/index.cjs +48 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +48 -12
- package/dist/index.js.map +1 -1
- package/package.json +14 -5
- package/prices.json +48 -12
package/dist/index.cjs
CHANGED
|
@@ -203,18 +203,54 @@ var prices_default = {
|
|
|
203
203
|
updated_at: "2026-04-16",
|
|
204
204
|
source: "https://raw.githubusercontent.com/diogonzafe/tokenwatch/main/prices.json",
|
|
205
205
|
models: {
|
|
206
|
-
"gpt-4o": {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
"gpt-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
"
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
206
|
+
"gpt-4o": {
|
|
207
|
+
input: 2.5,
|
|
208
|
+
output: 10
|
|
209
|
+
},
|
|
210
|
+
"gpt-4o-mini": {
|
|
211
|
+
input: 0.15,
|
|
212
|
+
output: 0.6
|
|
213
|
+
},
|
|
214
|
+
"gpt-5": {
|
|
215
|
+
input: 1.25,
|
|
216
|
+
output: 10
|
|
217
|
+
},
|
|
218
|
+
"gpt-5-mini": {
|
|
219
|
+
input: 0.25,
|
|
220
|
+
output: 2
|
|
221
|
+
},
|
|
222
|
+
"gpt-5-nano": {
|
|
223
|
+
input: 0.05,
|
|
224
|
+
output: 0.4
|
|
225
|
+
},
|
|
226
|
+
"claude-opus-4-6": {
|
|
227
|
+
input: 5,
|
|
228
|
+
output: 25
|
|
229
|
+
},
|
|
230
|
+
"claude-sonnet-4-6": {
|
|
231
|
+
input: 3,
|
|
232
|
+
output: 15
|
|
233
|
+
},
|
|
234
|
+
"claude-haiku-4-5": {
|
|
235
|
+
input: 1,
|
|
236
|
+
output: 5
|
|
237
|
+
},
|
|
238
|
+
"gemini-2.5-pro": {
|
|
239
|
+
input: 1.25,
|
|
240
|
+
output: 10
|
|
241
|
+
},
|
|
242
|
+
"gemini-2.5-flash": {
|
|
243
|
+
input: 0.3,
|
|
244
|
+
output: 2.5
|
|
245
|
+
},
|
|
246
|
+
"deepseek-chat": {
|
|
247
|
+
input: 0.28,
|
|
248
|
+
output: 0.42
|
|
249
|
+
},
|
|
250
|
+
"deepseek-reasoner": {
|
|
251
|
+
input: 0.55,
|
|
252
|
+
output: 2.19
|
|
253
|
+
}
|
|
218
254
|
}
|
|
219
255
|
};
|
|
220
256
|
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/core/tracker.ts","../src/core/pricing.ts","../src/core/storage.ts","../src/core/sync.ts","../prices.json","../src/providers/openai.ts","../src/providers/anthropic.ts","../src/providers/gemini.ts"],"sourcesContent":["export { createTracker } from './core/tracker.js'\nexport { wrapOpenAI } from './providers/openai.js'\nexport { wrapAnthropic } from './providers/anthropic.js'\nexport { wrapGemini } from './providers/gemini.js'\nexport { wrapDeepSeek } from './providers/deepseek.js'\n\nexport type {\n Tracker,\n TrackerConfig,\n UsageEntry,\n Report,\n ModelStats,\n SessionStats,\n UserStats,\n ModelPrice,\n PriceMap,\n PricesFile,\n IStorage,\n TrackingMeta,\n} from './types/index.js'\n","import { z } from 'zod'\nimport type {\n Tracker,\n TrackerConfig,\n UsageEntry,\n Report,\n ModelStats,\n SessionStats,\n UserStats,\n PriceMap,\n} from '../types/index.js'\nimport { resolvePrice, calculateCost } from './pricing.js'\nimport { createStorage } from './storage.js'\nimport { getRemotePrices } from './sync.js'\nimport bundledPricesFile from '../../prices.json' assert { type: 'json' }\n\nconst bundledPrices: PriceMap = bundledPricesFile.models as PriceMap\n\n// ─── Config validation schema ─────────────────────────────────────────────────\n\nconst ModelPriceSchema = z.object({\n input: z.number().nonnegative(),\n output: z.number().nonnegative(),\n})\n\nconst TrackerConfigSchema = z.object({\n storage: z.enum(['memory', 'sqlite']).optional().default('memory'),\n alertThreshold: z.number().positive().optional(),\n webhookUrl: z.string().url().optional(),\n syncPrices: z.boolean().optional().default(true),\n customPrices: z.record(z.string(), ModelPriceSchema).optional(),\n})\n\nexport function createTracker(config: TrackerConfig = {}): Tracker {\n const parsed = TrackerConfigSchema.safeParse(config)\n if (!parsed.success) {\n const issues = parsed.error.issues.map((i) => ` ${i.path.join('.')}: ${i.message}`).join('\\n')\n throw new Error(`[tokenwatch] Invalid config:\\n${issues}`)\n }\n\n const {\n storage: storageType,\n alertThreshold,\n webhookUrl,\n syncPrices,\n customPrices,\n } = parsed.data\n\n const storage = createStorage(storageType)\n\n // Fetch remote prices in the background — bundled prices are used as fallback\n // until the sync resolves. Zero latency added to createTracker().\n let remotePrices: PriceMap | undefined\n if (syncPrices) {\n getRemotePrices()\n .then((result) => {\n if (result) remotePrices = result\n })\n .catch(() => {\n // best-effort — bundled prices remain in use\n })\n }\n\n let alertFired = false\n const startedAt = new Date().toISOString()\n\n function resolveModelPrice(model: string) {\n return resolvePrice(model, {\n bundledPrices,\n ...(customPrices !== undefined && { customPrices }),\n ...(remotePrices !== undefined && { remotePrices }),\n })\n }\n\n function track(entry: Omit<UsageEntry, 'costUSD' | 'timestamp'>): void {\n const price = resolveModelPrice(entry.model)\n const costUSD = calculateCost(entry.inputTokens, entry.outputTokens, price)\n const full: UsageEntry = {\n ...entry,\n costUSD,\n timestamp: new Date().toISOString(),\n }\n storage.record(full)\n maybeFireAlert()\n }\n\n function maybeFireAlert(): void {\n if (!alertThreshold || !webhookUrl || alertFired) return\n const total = computeTotal(storage.getAll())\n if (total >= alertThreshold) {\n alertFired = true\n const payload = {\n text: `[tokenwatch] Alert: total cost reached $${total.toFixed(4)} USD (threshold: $${alertThreshold})`,\n }\n fetch(webhookUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }).catch(() => {\n // fire-and-forget\n })\n }\n }\n\n function getReport(): Report {\n const entries = storage.getAll()\n const byModel: Record<string, ModelStats> = {}\n const bySession: Record<string, SessionStats> = {}\n const byUser: Record<string, UserStats> = {}\n\n let totalInput = 0\n let totalOutput = 0\n let totalCost = 0\n let lastTimestamp = startedAt\n\n for (const e of entries) {\n totalInput += e.inputTokens\n totalOutput += e.outputTokens\n totalCost += e.costUSD\n if (e.timestamp > lastTimestamp) lastTimestamp = e.timestamp\n\n // byModel\n const m = (byModel[e.model] ??= { costUSD: 0, calls: 0, tokens: { input: 0, output: 0 } })\n m.costUSD += e.costUSD\n m.calls += 1\n m.tokens.input += e.inputTokens\n m.tokens.output += e.outputTokens\n\n // bySession\n if (e.sessionId) {\n const s = (bySession[e.sessionId] ??= { costUSD: 0, calls: 0 })\n s.costUSD += e.costUSD\n s.calls += 1\n }\n\n // byUser\n if (e.userId) {\n const u = (byUser[e.userId] ??= { costUSD: 0, calls: 0 })\n u.costUSD += e.costUSD\n u.calls += 1\n }\n }\n\n return {\n totalCostUSD: totalCost,\n totalTokens: { input: totalInput, output: totalOutput },\n byModel,\n bySession,\n byUser,\n period: { from: startedAt, to: lastTimestamp },\n }\n }\n\n function reset(): void {\n storage.clearAll()\n alertFired = false\n }\n\n function resetSession(sessionId: string): void {\n storage.clearSession(sessionId)\n }\n\n function exportJSON(): string {\n return JSON.stringify(getReport(), null, 2)\n }\n\n function exportCSV(): string {\n const entries = storage.getAll()\n const header = 'timestamp,model,inputTokens,outputTokens,costUSD,sessionId,userId'\n const rows = entries.map((e) =>\n [\n e.timestamp,\n e.model,\n e.inputTokens,\n e.outputTokens,\n e.costUSD.toFixed(8),\n e.sessionId ?? '',\n e.userId ?? '',\n ].join(','),\n )\n return [header, ...rows].join('\\n')\n }\n\n return { track, getReport, reset, resetSession, exportJSON, exportCSV }\n}\n\nfunction computeTotal(entries: UsageEntry[]): number {\n return entries.reduce((sum, e) => sum + e.costUSD, 0)\n}\n","import type { ModelPrice, PriceMap } from '../types/index.js'\n\n/**\n * Resolve price for a model using 3-layer priority:\n * 1. customPrices (user override)\n * 2. remotePrices (synced from GitHub, cached 24h)\n * 3. bundledPrices (always-present fallback)\n *\n * Falls back to zero-cost with a console warning when model is not found anywhere.\n */\nexport function resolvePrice(\n model: string,\n layers: {\n customPrices?: PriceMap\n remotePrices?: PriceMap\n bundledPrices: PriceMap\n },\n): ModelPrice {\n const { customPrices, remotePrices, bundledPrices } = layers\n\n const found =\n lookupInMap(model, customPrices) ??\n lookupInMap(model, remotePrices) ??\n lookupInMap(model, bundledPrices)\n\n if (found) return found\n\n console.warn(\n `[tokenwatch] Unknown model \"${model}\". Cost will be recorded as $0. ` +\n `Add it via customPrices or update prices with: tokenwatch sync`,\n )\n return { input: 0, output: 0 }\n}\n\n/**\n * Look up a model in a PriceMap using:\n * 1. exact key match\n * 2. prefix match — map key is a prefix of the model string (e.g. \"gpt-4o\" matches \"gpt-4o-2024-11-20\")\n * 3. reverse prefix — model string is a prefix of a map key (unusual, safety net)\n */\nfunction lookupInMap(model: string, map: PriceMap | undefined): ModelPrice | undefined {\n if (!map) return undefined\n\n if (model in map) return map[model]\n\n // prefix match\n for (const key of Object.keys(map)) {\n if (model.startsWith(key) || key.startsWith(model)) {\n return map[key]\n }\n }\n\n return undefined\n}\n\n/**\n * Calculate cost in USD given token counts and per-million-token prices.\n */\nexport function calculateCost(\n inputTokens: number,\n outputTokens: number,\n price: ModelPrice,\n): number {\n return (inputTokens / 1_000_000) * price.input + (outputTokens / 1_000_000) * price.output\n}\n","import { createRequire } from 'node:module'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport { mkdirSync } from 'node:fs'\nimport type { IStorage, UsageEntry } from '../types/index.js'\n\n// ─── Memory storage ───────────────────────────────────────────────────────────\n\nexport class MemoryStorage implements IStorage {\n private entries: UsageEntry[] = []\n\n record(entry: UsageEntry): void {\n this.entries.push(entry)\n }\n\n getAll(): UsageEntry[] {\n return [...this.entries]\n }\n\n clearAll(): void {\n this.entries = []\n }\n\n clearSession(sessionId: string): void {\n this.entries = this.entries.filter((e) => e.sessionId !== sessionId)\n }\n}\n\n// ─── SQLite storage ───────────────────────────────────────────────────────────\n\nconst DB_DIR = join(homedir(), '.tokenwatch')\nconst DB_PATH = join(DB_DIR, 'usage.db')\n\nexport class SqliteStorage implements IStorage {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private db: any\n\n constructor(dbPath = DB_PATH) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let BetterSqlite3: any\n try {\n // In CJS context globalThis.require is the native require; in ESM use createRequire.\n // This makes the lazy load work in both output formats produced by tsup.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const req: NodeRequire =\n typeof (globalThis as any).require === 'function'\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (globalThis as any).require\n : createRequire(import.meta.url)\n BetterSqlite3 = req('better-sqlite3')\n } catch {\n throw new Error(\n '[tokenwatch] SQLite storage requires better-sqlite3. ' +\n 'Run: npm install better-sqlite3',\n )\n }\n\n mkdirSync(DB_DIR, { recursive: true })\n this.db = new BetterSqlite3(dbPath)\n this.migrate()\n }\n\n private migrate(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS usage (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n model TEXT NOT NULL,\n input_tokens INTEGER NOT NULL,\n output_tokens INTEGER NOT NULL,\n cost_usd REAL NOT NULL,\n session_id TEXT,\n user_id TEXT,\n timestamp TEXT NOT NULL\n )\n `)\n }\n\n record(entry: UsageEntry): void {\n this.db\n .prepare(\n `INSERT INTO usage\n (model, input_tokens, output_tokens, cost_usd, session_id, user_id, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\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\n getAll(): UsageEntry[] {\n const rows = this.db.prepare('SELECT * FROM usage').all() as Array<{\n model: string\n input_tokens: number\n output_tokens: number\n cost_usd: number\n session_id: string | null\n user_id: string | null\n timestamp: string\n }>\n\n return rows.map((r) => ({\n model: r.model,\n inputTokens: r.input_tokens,\n outputTokens: r.output_tokens,\n costUSD: r.cost_usd,\n ...(r.session_id != null && { sessionId: r.session_id }),\n ...(r.user_id != null && { userId: r.user_id }),\n timestamp: r.timestamp,\n }))\n }\n\n clearAll(): void {\n this.db.exec('DELETE FROM usage')\n }\n\n clearSession(sessionId: string): void {\n this.db.prepare('DELETE FROM usage WHERE session_id = ?').run(sessionId)\n }\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\nexport function createStorage(type: 'memory' | 'sqlite'): IStorage {\n if (type === 'sqlite') return new SqliteStorage()\n return new MemoryStorage()\n}\n","import { readFile, writeFile, mkdir } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport type { PricesFile, PriceMap } from '../types/index.js'\n\nconst CACHE_DIR = join(homedir(), '.tokenwatch')\nconst CACHE_FILE = join(CACHE_DIR, 'prices.json')\nconst CACHE_TTL_MS = 24 * 60 * 60 * 1000 // 24 hours\nconst REMOTE_URL =\n 'https://raw.githubusercontent.com/diogonzafe/tokenwatch/main/prices.json'\n\nexport async function fetchRemotePrices(url = REMOTE_URL): Promise<PriceMap | null> {\n try {\n const res = await fetch(url, { signal: AbortSignal.timeout(8_000) })\n if (!res.ok) return null\n const data = (await res.json()) as PricesFile\n if (!data?.models) return null\n await persistCache(data)\n return data.models\n } catch {\n return null\n }\n}\n\nexport async function loadCachedPrices(): Promise<PriceMap | null> {\n if (!existsSync(CACHE_FILE)) return null\n try {\n const raw = await readFile(CACHE_FILE, 'utf8')\n const data = JSON.parse(raw) as PricesFile & { _cachedAt?: number }\n const age = Date.now() - (data._cachedAt ?? 0)\n if (age > CACHE_TTL_MS) return null\n return data.models ?? null\n } catch {\n return null\n }\n}\n\nasync function persistCache(data: PricesFile): Promise<void> {\n try {\n await mkdir(CACHE_DIR, { recursive: true })\n const payload = { ...data, _cachedAt: Date.now() }\n await writeFile(CACHE_FILE, JSON.stringify(payload, null, 2), 'utf8')\n } catch {\n // best-effort — never throw\n }\n}\n\n/**\n * Returns the best available remote price map:\n * 1. Valid local cache (< 24h)\n * 2. Fresh remote fetch (also updates cache)\n * 3. null if both fail\n */\nexport async function getRemotePrices(): Promise<PriceMap | null> {\n const cached = await loadCachedPrices()\n if (cached) return cached\n return fetchRemotePrices()\n}\n","{\n \"updated_at\": \"2026-04-16\",\n \"source\": \"https://raw.githubusercontent.com/diogonzafe/tokenwatch/main/prices.json\",\n \"models\": {\n \"gpt-4o\": { \"input\": 2.50, \"output\": 10.00 },\n \"gpt-4o-mini\": { \"input\": 0.15, \"output\": 0.60 },\n \"gpt-5\": { \"input\": 1.25, \"output\": 10.00 },\n \"gpt-5-mini\": { \"input\": 0.25, \"output\": 2.00 },\n \"gpt-5-nano\": { \"input\": 0.05, \"output\": 0.40 },\n \"claude-opus-4-6\": { \"input\": 5.00, \"output\": 25.00 },\n \"claude-sonnet-4-6\": { \"input\": 3.00, \"output\": 15.00 },\n \"claude-haiku-4-5\": { \"input\": 1.00, \"output\": 5.00 },\n \"gemini-2.5-pro\": { \"input\": 1.25, \"output\": 10.00 },\n \"gemini-2.5-flash\": { \"input\": 0.30, \"output\": 2.50 },\n \"deepseek-chat\": { \"input\": 0.28, \"output\": 0.42 },\n \"deepseek-reasoner\": { \"input\": 0.55, \"output\": 2.19 }\n }\n}\n","import type { Tracker, TrackingMeta } from '../types/index.js'\n\n// ─── Minimal structural types (no hard dep on openai package) ────────────────\n\ninterface Usage {\n prompt_tokens?: number\n completion_tokens?: number\n input_tokens?: number\n output_tokens?: number\n}\n\ninterface Completion {\n model?: string\n usage?: Usage | null\n}\n\ninterface StreamChunk {\n model?: string\n usage?: Usage | null\n}\n\ninterface CompletionsLike {\n create(params: Record<string, unknown>): Promise<unknown>\n}\n\ninterface ChatLike {\n completions: CompletionsLike\n}\n\ntype OpenAILike = { chat: ChatLike } & Record<string, unknown>\n\n// ─── Augmented return type ────────────────────────────────────────────────────\n// The wrapped client's create() accepts TrackingMeta fields (__sessionId, __userId)\n// in addition to the original params — no `as any` needed at the call site.\n\ntype AugmentedCreate<TCreate extends (...args: any[]) => any> = (\n params: Parameters<TCreate>[0] & TrackingMeta,\n) => ReturnType<TCreate>\n\ntype WrappedOpenAI<T extends OpenAILike> = Omit<T, 'chat'> & {\n chat: Omit<T['chat'], 'completions'> & {\n completions: Omit<T['chat']['completions'], 'create'> & {\n create: AugmentedCreate<T['chat']['completions']['create']>\n }\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction extractMeta(params: Record<string, unknown>): {\n cleaned: Record<string, unknown>\n sessionId: string | undefined\n userId: string | undefined\n} {\n const { __sessionId, __userId, ...cleaned } = params as Record<string, unknown> & TrackingMeta\n return {\n cleaned,\n sessionId: typeof __sessionId === 'string' ? __sessionId : undefined,\n userId: typeof __userId === 'string' ? __userId : undefined,\n }\n}\n\nfunction extractUsage(usage: Usage | null | undefined): {\n inputTokens: number\n outputTokens: number\n} {\n if (!usage) return { inputTokens: 0, outputTokens: 0 }\n return {\n inputTokens: usage.prompt_tokens ?? usage.input_tokens ?? 0,\n outputTokens: usage.completion_tokens ?? usage.output_tokens ?? 0,\n }\n}\n\nfunction trackWithMeta(\n tracker: Tracker,\n model: string,\n inputTokens: number,\n outputTokens: number,\n sessionId: string | undefined,\n userId: string | undefined,\n): void {\n tracker.track({\n model,\n inputTokens,\n outputTokens,\n ...(sessionId !== undefined && { sessionId }),\n ...(userId !== undefined && { userId }),\n })\n}\n\n// ─── Streaming wrapper ────────────────────────────────────────────────────────\n\nasync function* wrapStream(\n stream: AsyncIterable<StreamChunk>,\n model: string,\n sessionId: string | undefined,\n userId: string | undefined,\n tracker: Tracker,\n): AsyncGenerator<StreamChunk> {\n let lastChunk: StreamChunk | undefined\n for await (const chunk of stream) {\n lastChunk = chunk\n yield chunk\n }\n if (lastChunk?.usage) {\n const { inputTokens, outputTokens } = extractUsage(lastChunk.usage)\n trackWithMeta(tracker, model, inputTokens, outputTokens, sessionId, userId)\n }\n}\n\n// ─── Public wrapper ───────────────────────────────────────────────────────────\n\n/**\n * Wraps an OpenAI client (or any OpenAI-compatible client) to transparently\n * intercept chat.completions.create calls and report token usage to the tracker.\n *\n * The returned client is typed to accept __sessionId and __userId alongside the\n * normal params — no type cast required at the call site.\n */\nexport function wrapOpenAI<T extends OpenAILike>(client: T, tracker: Tracker): WrappedOpenAI<T> {\n const proxiedCompletions = new Proxy(client.chat.completions, {\n get(target, prop) {\n if (prop !== 'create')\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n\n return async function (params: Record<string, unknown>) {\n const { cleaned, sessionId, userId } = extractMeta(params)\n const model = typeof cleaned['model'] === 'string' ? cleaned['model'] : 'unknown'\n\n let result: unknown\n try {\n result = await (target as CompletionsLike).create(cleaned)\n } catch (err) {\n throw err\n }\n\n if (result && typeof result === 'object' && Symbol.asyncIterator in result) {\n return wrapStream(\n result as AsyncIterable<StreamChunk>,\n model,\n sessionId,\n userId,\n tracker,\n )\n }\n\n const completion = result as Completion\n const { inputTokens, outputTokens } = extractUsage(completion.usage)\n trackWithMeta(\n tracker,\n completion.model ?? model,\n inputTokens,\n outputTokens,\n sessionId,\n userId,\n )\n\n return result\n }\n },\n })\n\n const proxiedChat = new Proxy(client.chat, {\n get(target, prop) {\n if (prop === 'completions') return proxiedCompletions\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n },\n })\n\n return new Proxy(client, {\n get(target, prop) {\n if (prop === 'chat') return proxiedChat\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n },\n }) as unknown as WrappedOpenAI<T>\n}\n","import type { Tracker, TrackingMeta } from '../types/index.js'\n\n// ─── Minimal structural types ─────────────────────────────────────────────────\n\ninterface AnthropicUsage {\n input_tokens?: number\n output_tokens?: number\n}\n\ninterface AnthropicMessage {\n model?: string\n usage?: AnthropicUsage | null\n}\n\ninterface AnthropicStreamEvent {\n type?: string\n usage?: AnthropicUsage | null\n message?: AnthropicMessage\n}\n\ninterface MessagesLike {\n create(params: Record<string, unknown>): Promise<unknown>\n}\n\ntype AnthropicLike = { messages: MessagesLike } & Record<string, unknown>\n\n// ─── Augmented return type ────────────────────────────────────────────────────\n\ntype AugmentedCreate<TCreate extends (...args: any[]) => any> = (\n params: Parameters<TCreate>[0] & TrackingMeta,\n) => ReturnType<TCreate>\n\ntype WrappedAnthropic<T extends AnthropicLike> = Omit<T, 'messages'> & {\n messages: Omit<T['messages'], 'create'> & {\n create: AugmentedCreate<T['messages']['create']>\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction extractMeta(params: Record<string, unknown>): {\n cleaned: Record<string, unknown>\n sessionId: string | undefined\n userId: string | undefined\n} {\n const { __sessionId, __userId, ...cleaned } = params as Record<string, unknown> & TrackingMeta\n return {\n cleaned,\n sessionId: typeof __sessionId === 'string' ? __sessionId : undefined,\n userId: typeof __userId === 'string' ? __userId : undefined,\n }\n}\n\nfunction extractUsage(usage: AnthropicUsage | null | undefined): {\n inputTokens: number\n outputTokens: number\n} {\n if (!usage) return { inputTokens: 0, outputTokens: 0 }\n return {\n inputTokens: usage.input_tokens ?? 0,\n outputTokens: usage.output_tokens ?? 0,\n }\n}\n\nfunction trackWithMeta(\n tracker: Tracker,\n model: string,\n inputTokens: number,\n outputTokens: number,\n sessionId: string | undefined,\n userId: string | undefined,\n): void {\n tracker.track({\n model,\n inputTokens,\n outputTokens,\n ...(sessionId !== undefined && { sessionId }),\n ...(userId !== undefined && { userId }),\n })\n}\n\n// ─── Streaming wrapper ────────────────────────────────────────────────────────\n\nasync function* wrapStream(\n stream: AsyncIterable<AnthropicStreamEvent>,\n model: string,\n sessionId: string | undefined,\n userId: string | undefined,\n tracker: Tracker,\n): AsyncGenerator<AnthropicStreamEvent> {\n let inputTokens = 0\n let outputTokens = 0\n\n for await (const event of stream) {\n yield event\n\n if (event.type === 'message_start' && event.message?.usage) {\n inputTokens = event.message.usage.input_tokens ?? 0\n }\n if (event.type === 'message_delta' && event.usage) {\n outputTokens = event.usage.output_tokens ?? 0\n }\n }\n\n if (inputTokens > 0 || outputTokens > 0) {\n trackWithMeta(tracker, model, inputTokens, outputTokens, sessionId, userId)\n }\n}\n\n// ─── Public wrapper ───────────────────────────────────────────────────────────\n\n/**\n * Wraps an Anthropic client to transparently intercept messages.create calls\n * and report token usage to the tracker.\n *\n * The returned client is typed to accept __sessionId and __userId alongside the\n * normal params — no type cast required at the call site.\n */\nexport function wrapAnthropic<T extends AnthropicLike>(\n client: T,\n tracker: Tracker,\n): WrappedAnthropic<T> {\n const proxiedMessages = new Proxy(client.messages, {\n get(target, prop) {\n if (prop !== 'create')\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n\n return async function (params: Record<string, unknown>) {\n const { cleaned, sessionId, userId } = extractMeta(params)\n const model = typeof cleaned['model'] === 'string' ? cleaned['model'] : 'unknown'\n\n let result: unknown\n try {\n result = await (target as MessagesLike).create(cleaned)\n } catch (err) {\n throw err\n }\n\n if (result && typeof result === 'object' && Symbol.asyncIterator in result) {\n return wrapStream(\n result as AsyncIterable<AnthropicStreamEvent>,\n model,\n sessionId,\n userId,\n tracker,\n )\n }\n\n const message = result as AnthropicMessage\n const { inputTokens, outputTokens } = extractUsage(message.usage)\n trackWithMeta(\n tracker,\n message.model ?? model,\n inputTokens,\n outputTokens,\n sessionId,\n userId,\n )\n\n return result\n }\n },\n })\n\n return new Proxy(client, {\n get(target, prop) {\n if (prop === 'messages') return proxiedMessages\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n },\n }) as unknown as WrappedAnthropic<T>\n}\n","import type { Tracker } from '../types/index.js'\n\n// ─── Minimal structural types ─────────────────────────────────────────────────\n\ninterface UsageMetadata {\n promptTokenCount?: number\n candidatesTokenCount?: number\n totalTokenCount?: number\n}\n\ninterface GenerateContentResponse {\n usageMetadata?: UsageMetadata | null\n}\n\ninterface GenerativeModelLike {\n generateContent(params: unknown): Promise<{ response: GenerateContentResponse }>\n generateContentStream(\n params: unknown,\n ): Promise<{\n stream: AsyncIterable<{ usageMetadata?: UsageMetadata | null }>\n response: Promise<GenerateContentResponse>\n }>\n model?: string\n}\n\ninterface GenAILike {\n getGenerativeModel(params: { model: string } & Record<string, unknown>): GenerativeModelLike\n}\n\n// ─── Public wrapper ───────────────────────────────────────────────────────────\n\n/**\n * Wraps a GoogleGenerativeAI client to transparently intercept\n * generateContent / generateContentStream calls and report token usage.\n *\n * Returns the same type T that was passed in.\n */\nexport function wrapGemini<T extends GenAILike>(client: T, tracker: Tracker): T {\n return new Proxy(client, {\n get(target, prop) {\n if (prop !== 'getGenerativeModel')\n return (target as Record<string | symbol, unknown>)[prop]\n\n return function (modelParams: { model: string } & Record<string, unknown>) {\n const modelInstance = target.getGenerativeModel(modelParams)\n const modelId = modelParams.model\n\n return new Proxy(modelInstance, {\n get(mTarget, mProp) {\n if (mProp === 'generateContent') {\n return async function (params: unknown) {\n let result: { response: GenerateContentResponse }\n try {\n result = await mTarget.generateContent(params)\n } catch (err) {\n throw err\n }\n\n const meta = result.response.usageMetadata\n tracker.track({\n model: modelId,\n inputTokens: meta?.promptTokenCount ?? 0,\n outputTokens: meta?.candidatesTokenCount ?? 0,\n })\n\n return result\n }\n }\n\n if (mProp === 'generateContentStream') {\n return async function (params: unknown) {\n let streamResult: {\n stream: AsyncIterable<{ usageMetadata?: UsageMetadata | null }>\n response: Promise<GenerateContentResponse>\n }\n try {\n streamResult = await mTarget.generateContentStream(params)\n } catch (err) {\n throw err\n }\n\n // Consume usage from the resolved response promise after streaming\n streamResult.response\n .then((res) => {\n const meta = res.usageMetadata\n tracker.track({\n model: modelId,\n inputTokens: meta?.promptTokenCount ?? 0,\n outputTokens: meta?.candidatesTokenCount ?? 0,\n })\n })\n .catch(() => {\n // best-effort\n })\n\n return streamResult\n }\n }\n\n return (mTarget as unknown as Record<string | symbol, unknown>)[mProp]\n },\n })\n }\n },\n }) as T\n}\n"],"mappings":"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","names":["bundledPrices","import_node_fs","import_node_os","import_node_path","extractMeta","extractUsage","trackWithMeta","wrapStream"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/tracker.ts","../src/core/pricing.ts","../src/core/storage.ts","../src/core/sync.ts","../prices.json","../src/providers/openai.ts","../src/providers/anthropic.ts","../src/providers/gemini.ts"],"sourcesContent":["export { createTracker } from './core/tracker.js'\nexport { wrapOpenAI } from './providers/openai.js'\nexport { wrapAnthropic } from './providers/anthropic.js'\nexport { wrapGemini } from './providers/gemini.js'\nexport { wrapDeepSeek } from './providers/deepseek.js'\n\nexport type {\n Tracker,\n TrackerConfig,\n UsageEntry,\n Report,\n ModelStats,\n SessionStats,\n UserStats,\n ModelPrice,\n PriceMap,\n PricesFile,\n IStorage,\n TrackingMeta,\n} from './types/index.js'\n","import { z } from 'zod'\nimport type {\n Tracker,\n TrackerConfig,\n UsageEntry,\n Report,\n ModelStats,\n SessionStats,\n UserStats,\n PriceMap,\n} from '../types/index.js'\nimport { resolvePrice, calculateCost } from './pricing.js'\nimport { createStorage } from './storage.js'\nimport { getRemotePrices } from './sync.js'\nimport bundledPricesFile from '../../prices.json' assert { type: 'json' }\n\nconst bundledPrices: PriceMap = bundledPricesFile.models as PriceMap\n\n// ─── Config validation schema ─────────────────────────────────────────────────\n\nconst ModelPriceSchema = z.object({\n input: z.number().nonnegative(),\n output: z.number().nonnegative(),\n})\n\nconst TrackerConfigSchema = z.object({\n storage: z.enum(['memory', 'sqlite']).optional().default('memory'),\n alertThreshold: z.number().positive().optional(),\n webhookUrl: z.string().url().optional(),\n syncPrices: z.boolean().optional().default(true),\n customPrices: z.record(z.string(), ModelPriceSchema).optional(),\n})\n\nexport function createTracker(config: TrackerConfig = {}): Tracker {\n const parsed = TrackerConfigSchema.safeParse(config)\n if (!parsed.success) {\n const issues = parsed.error.issues.map((i) => ` ${i.path.join('.')}: ${i.message}`).join('\\n')\n throw new Error(`[tokenwatch] Invalid config:\\n${issues}`)\n }\n\n const {\n storage: storageType,\n alertThreshold,\n webhookUrl,\n syncPrices,\n customPrices,\n } = parsed.data\n\n const storage = createStorage(storageType)\n\n // Fetch remote prices in the background — bundled prices are used as fallback\n // until the sync resolves. Zero latency added to createTracker().\n let remotePrices: PriceMap | undefined\n if (syncPrices) {\n getRemotePrices()\n .then((result) => {\n if (result) remotePrices = result\n })\n .catch(() => {\n // best-effort — bundled prices remain in use\n })\n }\n\n let alertFired = false\n const startedAt = new Date().toISOString()\n\n function resolveModelPrice(model: string) {\n return resolvePrice(model, {\n bundledPrices,\n ...(customPrices !== undefined && { customPrices }),\n ...(remotePrices !== undefined && { remotePrices }),\n })\n }\n\n function track(entry: Omit<UsageEntry, 'costUSD' | 'timestamp'>): void {\n const price = resolveModelPrice(entry.model)\n const costUSD = calculateCost(entry.inputTokens, entry.outputTokens, price)\n const full: UsageEntry = {\n ...entry,\n costUSD,\n timestamp: new Date().toISOString(),\n }\n storage.record(full)\n maybeFireAlert()\n }\n\n function maybeFireAlert(): void {\n if (!alertThreshold || !webhookUrl || alertFired) return\n const total = computeTotal(storage.getAll())\n if (total >= alertThreshold) {\n alertFired = true\n const payload = {\n text: `[tokenwatch] Alert: total cost reached $${total.toFixed(4)} USD (threshold: $${alertThreshold})`,\n }\n fetch(webhookUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }).catch(() => {\n // fire-and-forget\n })\n }\n }\n\n function getReport(): Report {\n const entries = storage.getAll()\n const byModel: Record<string, ModelStats> = {}\n const bySession: Record<string, SessionStats> = {}\n const byUser: Record<string, UserStats> = {}\n\n let totalInput = 0\n let totalOutput = 0\n let totalCost = 0\n let lastTimestamp = startedAt\n\n for (const e of entries) {\n totalInput += e.inputTokens\n totalOutput += e.outputTokens\n totalCost += e.costUSD\n if (e.timestamp > lastTimestamp) lastTimestamp = e.timestamp\n\n // byModel\n const m = (byModel[e.model] ??= { costUSD: 0, calls: 0, tokens: { input: 0, output: 0 } })\n m.costUSD += e.costUSD\n m.calls += 1\n m.tokens.input += e.inputTokens\n m.tokens.output += e.outputTokens\n\n // bySession\n if (e.sessionId) {\n const s = (bySession[e.sessionId] ??= { costUSD: 0, calls: 0 })\n s.costUSD += e.costUSD\n s.calls += 1\n }\n\n // byUser\n if (e.userId) {\n const u = (byUser[e.userId] ??= { costUSD: 0, calls: 0 })\n u.costUSD += e.costUSD\n u.calls += 1\n }\n }\n\n return {\n totalCostUSD: totalCost,\n totalTokens: { input: totalInput, output: totalOutput },\n byModel,\n bySession,\n byUser,\n period: { from: startedAt, to: lastTimestamp },\n }\n }\n\n function reset(): void {\n storage.clearAll()\n alertFired = false\n }\n\n function resetSession(sessionId: string): void {\n storage.clearSession(sessionId)\n }\n\n function exportJSON(): string {\n return JSON.stringify(getReport(), null, 2)\n }\n\n function exportCSV(): string {\n const entries = storage.getAll()\n const header = 'timestamp,model,inputTokens,outputTokens,costUSD,sessionId,userId'\n const rows = entries.map((e) =>\n [\n e.timestamp,\n e.model,\n e.inputTokens,\n e.outputTokens,\n e.costUSD.toFixed(8),\n e.sessionId ?? '',\n e.userId ?? '',\n ].join(','),\n )\n return [header, ...rows].join('\\n')\n }\n\n return { track, getReport, reset, resetSession, exportJSON, exportCSV }\n}\n\nfunction computeTotal(entries: UsageEntry[]): number {\n return entries.reduce((sum, e) => sum + e.costUSD, 0)\n}\n","import type { ModelPrice, PriceMap } from '../types/index.js'\n\n/**\n * Resolve price for a model using 3-layer priority:\n * 1. customPrices (user override)\n * 2. remotePrices (synced from GitHub, cached 24h)\n * 3. bundledPrices (always-present fallback)\n *\n * Falls back to zero-cost with a console warning when model is not found anywhere.\n */\nexport function resolvePrice(\n model: string,\n layers: {\n customPrices?: PriceMap\n remotePrices?: PriceMap\n bundledPrices: PriceMap\n },\n): ModelPrice {\n const { customPrices, remotePrices, bundledPrices } = layers\n\n const found =\n lookupInMap(model, customPrices) ??\n lookupInMap(model, remotePrices) ??\n lookupInMap(model, bundledPrices)\n\n if (found) return found\n\n console.warn(\n `[tokenwatch] Unknown model \"${model}\". Cost will be recorded as $0. ` +\n `Add it via customPrices or update prices with: tokenwatch sync`,\n )\n return { input: 0, output: 0 }\n}\n\n/**\n * Look up a model in a PriceMap using:\n * 1. exact key match\n * 2. prefix match — map key is a prefix of the model string (e.g. \"gpt-4o\" matches \"gpt-4o-2024-11-20\")\n * 3. reverse prefix — model string is a prefix of a map key (unusual, safety net)\n */\nfunction lookupInMap(model: string, map: PriceMap | undefined): ModelPrice | undefined {\n if (!map) return undefined\n\n if (model in map) return map[model]\n\n // prefix match\n for (const key of Object.keys(map)) {\n if (model.startsWith(key) || key.startsWith(model)) {\n return map[key]\n }\n }\n\n return undefined\n}\n\n/**\n * Calculate cost in USD given token counts and per-million-token prices.\n */\nexport function calculateCost(\n inputTokens: number,\n outputTokens: number,\n price: ModelPrice,\n): number {\n return (inputTokens / 1_000_000) * price.input + (outputTokens / 1_000_000) * price.output\n}\n","import { createRequire } from 'node:module'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport { mkdirSync } from 'node:fs'\nimport type { IStorage, UsageEntry } from '../types/index.js'\n\n// ─── Memory storage ───────────────────────────────────────────────────────────\n\nexport class MemoryStorage implements IStorage {\n private entries: UsageEntry[] = []\n\n record(entry: UsageEntry): void {\n this.entries.push(entry)\n }\n\n getAll(): UsageEntry[] {\n return [...this.entries]\n }\n\n clearAll(): void {\n this.entries = []\n }\n\n clearSession(sessionId: string): void {\n this.entries = this.entries.filter((e) => e.sessionId !== sessionId)\n }\n}\n\n// ─── SQLite storage ───────────────────────────────────────────────────────────\n\nconst DB_DIR = join(homedir(), '.tokenwatch')\nconst DB_PATH = join(DB_DIR, 'usage.db')\n\nexport class SqliteStorage implements IStorage {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private db: any\n\n constructor(dbPath = DB_PATH) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let BetterSqlite3: any\n try {\n // In CJS context globalThis.require is the native require; in ESM use createRequire.\n // This makes the lazy load work in both output formats produced by tsup.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const req: NodeRequire =\n typeof (globalThis as any).require === 'function'\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (globalThis as any).require\n : createRequire(import.meta.url)\n BetterSqlite3 = req('better-sqlite3')\n } catch {\n throw new Error(\n '[tokenwatch] SQLite storage requires better-sqlite3. ' +\n 'Run: npm install better-sqlite3',\n )\n }\n\n mkdirSync(DB_DIR, { recursive: true })\n this.db = new BetterSqlite3(dbPath)\n this.migrate()\n }\n\n private migrate(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS usage (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n model TEXT NOT NULL,\n input_tokens INTEGER NOT NULL,\n output_tokens INTEGER NOT NULL,\n cost_usd REAL NOT NULL,\n session_id TEXT,\n user_id TEXT,\n timestamp TEXT NOT NULL\n )\n `)\n }\n\n record(entry: UsageEntry): void {\n this.db\n .prepare(\n `INSERT INTO usage\n (model, input_tokens, output_tokens, cost_usd, session_id, user_id, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\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\n getAll(): UsageEntry[] {\n const rows = this.db.prepare('SELECT * FROM usage').all() as Array<{\n model: string\n input_tokens: number\n output_tokens: number\n cost_usd: number\n session_id: string | null\n user_id: string | null\n timestamp: string\n }>\n\n return rows.map((r) => ({\n model: r.model,\n inputTokens: r.input_tokens,\n outputTokens: r.output_tokens,\n costUSD: r.cost_usd,\n ...(r.session_id != null && { sessionId: r.session_id }),\n ...(r.user_id != null && { userId: r.user_id }),\n timestamp: r.timestamp,\n }))\n }\n\n clearAll(): void {\n this.db.exec('DELETE FROM usage')\n }\n\n clearSession(sessionId: string): void {\n this.db.prepare('DELETE FROM usage WHERE session_id = ?').run(sessionId)\n }\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\nexport function createStorage(type: 'memory' | 'sqlite'): IStorage {\n if (type === 'sqlite') return new SqliteStorage()\n return new MemoryStorage()\n}\n","import { readFile, writeFile, mkdir } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport type { PricesFile, PriceMap } from '../types/index.js'\n\nconst CACHE_DIR = join(homedir(), '.tokenwatch')\nconst CACHE_FILE = join(CACHE_DIR, 'prices.json')\nconst CACHE_TTL_MS = 24 * 60 * 60 * 1000 // 24 hours\nconst REMOTE_URL =\n 'https://raw.githubusercontent.com/diogonzafe/tokenwatch/main/prices.json'\n\nexport async function fetchRemotePrices(url = REMOTE_URL): Promise<PriceMap | null> {\n try {\n const res = await fetch(url, { signal: AbortSignal.timeout(8_000) })\n if (!res.ok) return null\n const data = (await res.json()) as PricesFile\n if (!data?.models) return null\n await persistCache(data)\n return data.models\n } catch {\n return null\n }\n}\n\nexport async function loadCachedPrices(): Promise<PriceMap | null> {\n if (!existsSync(CACHE_FILE)) return null\n try {\n const raw = await readFile(CACHE_FILE, 'utf8')\n const data = JSON.parse(raw) as PricesFile & { _cachedAt?: number }\n const age = Date.now() - (data._cachedAt ?? 0)\n if (age > CACHE_TTL_MS) return null\n return data.models ?? null\n } catch {\n return null\n }\n}\n\nasync function persistCache(data: PricesFile): Promise<void> {\n try {\n await mkdir(CACHE_DIR, { recursive: true })\n const payload = { ...data, _cachedAt: Date.now() }\n await writeFile(CACHE_FILE, JSON.stringify(payload, null, 2), 'utf8')\n } catch {\n // best-effort — never throw\n }\n}\n\n/**\n * Returns the best available remote price map:\n * 1. Valid local cache (< 24h)\n * 2. Fresh remote fetch (also updates cache)\n * 3. null if both fail\n */\nexport async function getRemotePrices(): Promise<PriceMap | null> {\n const cached = await loadCachedPrices()\n if (cached) return cached\n return fetchRemotePrices()\n}\n","{\n \"updated_at\": \"2026-04-16\",\n \"source\": \"https://raw.githubusercontent.com/diogonzafe/tokenwatch/main/prices.json\",\n \"models\": {\n \"gpt-4o\": {\n \"input\": 2.5,\n \"output\": 10\n },\n \"gpt-4o-mini\": {\n \"input\": 0.15,\n \"output\": 0.6\n },\n \"gpt-5\": {\n \"input\": 1.25,\n \"output\": 10\n },\n \"gpt-5-mini\": {\n \"input\": 0.25,\n \"output\": 2\n },\n \"gpt-5-nano\": {\n \"input\": 0.05,\n \"output\": 0.4\n },\n \"claude-opus-4-6\": {\n \"input\": 5,\n \"output\": 25\n },\n \"claude-sonnet-4-6\": {\n \"input\": 3,\n \"output\": 15\n },\n \"claude-haiku-4-5\": {\n \"input\": 1,\n \"output\": 5\n },\n \"gemini-2.5-pro\": {\n \"input\": 1.25,\n \"output\": 10\n },\n \"gemini-2.5-flash\": {\n \"input\": 0.3,\n \"output\": 2.5\n },\n \"deepseek-chat\": {\n \"input\": 0.28,\n \"output\": 0.42\n },\n \"deepseek-reasoner\": {\n \"input\": 0.55,\n \"output\": 2.19\n }\n }\n}\n","import type { Tracker, TrackingMeta } from '../types/index.js'\n\n// ─── Minimal structural types (no hard dep on openai package) ────────────────\n\ninterface Usage {\n prompt_tokens?: number\n completion_tokens?: number\n input_tokens?: number\n output_tokens?: number\n}\n\ninterface Completion {\n model?: string\n usage?: Usage | null\n}\n\ninterface StreamChunk {\n model?: string\n usage?: Usage | null\n}\n\ninterface CompletionsLike {\n create(params: Record<string, unknown>): Promise<unknown>\n}\n\ninterface ChatLike {\n completions: CompletionsLike\n}\n\ntype OpenAILike = { chat: ChatLike } & Record<string, unknown>\n\n// ─── Augmented return type ────────────────────────────────────────────────────\n// The wrapped client's create() accepts TrackingMeta fields (__sessionId, __userId)\n// in addition to the original params — no `as any` needed at the call site.\n\ntype AugmentedCreate<TCreate extends (...args: any[]) => any> = (\n params: Parameters<TCreate>[0] & TrackingMeta,\n) => ReturnType<TCreate>\n\ntype WrappedOpenAI<T extends OpenAILike> = Omit<T, 'chat'> & {\n chat: Omit<T['chat'], 'completions'> & {\n completions: Omit<T['chat']['completions'], 'create'> & {\n create: AugmentedCreate<T['chat']['completions']['create']>\n }\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction extractMeta(params: Record<string, unknown>): {\n cleaned: Record<string, unknown>\n sessionId: string | undefined\n userId: string | undefined\n} {\n const { __sessionId, __userId, ...cleaned } = params as Record<string, unknown> & TrackingMeta\n return {\n cleaned,\n sessionId: typeof __sessionId === 'string' ? __sessionId : undefined,\n userId: typeof __userId === 'string' ? __userId : undefined,\n }\n}\n\nfunction extractUsage(usage: Usage | null | undefined): {\n inputTokens: number\n outputTokens: number\n} {\n if (!usage) return { inputTokens: 0, outputTokens: 0 }\n return {\n inputTokens: usage.prompt_tokens ?? usage.input_tokens ?? 0,\n outputTokens: usage.completion_tokens ?? usage.output_tokens ?? 0,\n }\n}\n\nfunction trackWithMeta(\n tracker: Tracker,\n model: string,\n inputTokens: number,\n outputTokens: number,\n sessionId: string | undefined,\n userId: string | undefined,\n): void {\n tracker.track({\n model,\n inputTokens,\n outputTokens,\n ...(sessionId !== undefined && { sessionId }),\n ...(userId !== undefined && { userId }),\n })\n}\n\n// ─── Streaming wrapper ────────────────────────────────────────────────────────\n\nasync function* wrapStream(\n stream: AsyncIterable<StreamChunk>,\n model: string,\n sessionId: string | undefined,\n userId: string | undefined,\n tracker: Tracker,\n): AsyncGenerator<StreamChunk> {\n let lastChunk: StreamChunk | undefined\n for await (const chunk of stream) {\n lastChunk = chunk\n yield chunk\n }\n if (lastChunk?.usage) {\n const { inputTokens, outputTokens } = extractUsage(lastChunk.usage)\n trackWithMeta(tracker, model, inputTokens, outputTokens, sessionId, userId)\n }\n}\n\n// ─── Public wrapper ───────────────────────────────────────────────────────────\n\n/**\n * Wraps an OpenAI client (or any OpenAI-compatible client) to transparently\n * intercept chat.completions.create calls and report token usage to the tracker.\n *\n * The returned client is typed to accept __sessionId and __userId alongside the\n * normal params — no type cast required at the call site.\n */\nexport function wrapOpenAI<T extends OpenAILike>(client: T, tracker: Tracker): WrappedOpenAI<T> {\n const proxiedCompletions = new Proxy(client.chat.completions, {\n get(target, prop) {\n if (prop !== 'create')\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n\n return async function (params: Record<string, unknown>) {\n const { cleaned, sessionId, userId } = extractMeta(params)\n const model = typeof cleaned['model'] === 'string' ? cleaned['model'] : 'unknown'\n\n let result: unknown\n try {\n result = await (target as CompletionsLike).create(cleaned)\n } catch (err) {\n throw err\n }\n\n if (result && typeof result === 'object' && Symbol.asyncIterator in result) {\n return wrapStream(\n result as AsyncIterable<StreamChunk>,\n model,\n sessionId,\n userId,\n tracker,\n )\n }\n\n const completion = result as Completion\n const { inputTokens, outputTokens } = extractUsage(completion.usage)\n trackWithMeta(\n tracker,\n completion.model ?? model,\n inputTokens,\n outputTokens,\n sessionId,\n userId,\n )\n\n return result\n }\n },\n })\n\n const proxiedChat = new Proxy(client.chat, {\n get(target, prop) {\n if (prop === 'completions') return proxiedCompletions\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n },\n })\n\n return new Proxy(client, {\n get(target, prop) {\n if (prop === 'chat') return proxiedChat\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n },\n }) as unknown as WrappedOpenAI<T>\n}\n","import type { Tracker, TrackingMeta } from '../types/index.js'\n\n// ─── Minimal structural types ─────────────────────────────────────────────────\n\ninterface AnthropicUsage {\n input_tokens?: number\n output_tokens?: number\n}\n\ninterface AnthropicMessage {\n model?: string\n usage?: AnthropicUsage | null\n}\n\ninterface AnthropicStreamEvent {\n type?: string\n usage?: AnthropicUsage | null\n message?: AnthropicMessage\n}\n\ninterface MessagesLike {\n create(params: Record<string, unknown>): Promise<unknown>\n}\n\ntype AnthropicLike = { messages: MessagesLike } & Record<string, unknown>\n\n// ─── Augmented return type ────────────────────────────────────────────────────\n\ntype AugmentedCreate<TCreate extends (...args: any[]) => any> = (\n params: Parameters<TCreate>[0] & TrackingMeta,\n) => ReturnType<TCreate>\n\ntype WrappedAnthropic<T extends AnthropicLike> = Omit<T, 'messages'> & {\n messages: Omit<T['messages'], 'create'> & {\n create: AugmentedCreate<T['messages']['create']>\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction extractMeta(params: Record<string, unknown>): {\n cleaned: Record<string, unknown>\n sessionId: string | undefined\n userId: string | undefined\n} {\n const { __sessionId, __userId, ...cleaned } = params as Record<string, unknown> & TrackingMeta\n return {\n cleaned,\n sessionId: typeof __sessionId === 'string' ? __sessionId : undefined,\n userId: typeof __userId === 'string' ? __userId : undefined,\n }\n}\n\nfunction extractUsage(usage: AnthropicUsage | null | undefined): {\n inputTokens: number\n outputTokens: number\n} {\n if (!usage) return { inputTokens: 0, outputTokens: 0 }\n return {\n inputTokens: usage.input_tokens ?? 0,\n outputTokens: usage.output_tokens ?? 0,\n }\n}\n\nfunction trackWithMeta(\n tracker: Tracker,\n model: string,\n inputTokens: number,\n outputTokens: number,\n sessionId: string | undefined,\n userId: string | undefined,\n): void {\n tracker.track({\n model,\n inputTokens,\n outputTokens,\n ...(sessionId !== undefined && { sessionId }),\n ...(userId !== undefined && { userId }),\n })\n}\n\n// ─── Streaming wrapper ────────────────────────────────────────────────────────\n\nasync function* wrapStream(\n stream: AsyncIterable<AnthropicStreamEvent>,\n model: string,\n sessionId: string | undefined,\n userId: string | undefined,\n tracker: Tracker,\n): AsyncGenerator<AnthropicStreamEvent> {\n let inputTokens = 0\n let outputTokens = 0\n\n for await (const event of stream) {\n yield event\n\n if (event.type === 'message_start' && event.message?.usage) {\n inputTokens = event.message.usage.input_tokens ?? 0\n }\n if (event.type === 'message_delta' && event.usage) {\n outputTokens = event.usage.output_tokens ?? 0\n }\n }\n\n if (inputTokens > 0 || outputTokens > 0) {\n trackWithMeta(tracker, model, inputTokens, outputTokens, sessionId, userId)\n }\n}\n\n// ─── Public wrapper ───────────────────────────────────────────────────────────\n\n/**\n * Wraps an Anthropic client to transparently intercept messages.create calls\n * and report token usage to the tracker.\n *\n * The returned client is typed to accept __sessionId and __userId alongside the\n * normal params — no type cast required at the call site.\n */\nexport function wrapAnthropic<T extends AnthropicLike>(\n client: T,\n tracker: Tracker,\n): WrappedAnthropic<T> {\n const proxiedMessages = new Proxy(client.messages, {\n get(target, prop) {\n if (prop !== 'create')\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n\n return async function (params: Record<string, unknown>) {\n const { cleaned, sessionId, userId } = extractMeta(params)\n const model = typeof cleaned['model'] === 'string' ? cleaned['model'] : 'unknown'\n\n let result: unknown\n try {\n result = await (target as MessagesLike).create(cleaned)\n } catch (err) {\n throw err\n }\n\n if (result && typeof result === 'object' && Symbol.asyncIterator in result) {\n return wrapStream(\n result as AsyncIterable<AnthropicStreamEvent>,\n model,\n sessionId,\n userId,\n tracker,\n )\n }\n\n const message = result as AnthropicMessage\n const { inputTokens, outputTokens } = extractUsage(message.usage)\n trackWithMeta(\n tracker,\n message.model ?? model,\n inputTokens,\n outputTokens,\n sessionId,\n userId,\n )\n\n return result\n }\n },\n })\n\n return new Proxy(client, {\n get(target, prop) {\n if (prop === 'messages') return proxiedMessages\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n },\n }) as unknown as WrappedAnthropic<T>\n}\n","import type { Tracker } from '../types/index.js'\n\n// ─── Minimal structural types ─────────────────────────────────────────────────\n\ninterface UsageMetadata {\n promptTokenCount?: number\n candidatesTokenCount?: number\n totalTokenCount?: number\n}\n\ninterface GenerateContentResponse {\n usageMetadata?: UsageMetadata | null\n}\n\ninterface GenerativeModelLike {\n generateContent(params: unknown): Promise<{ response: GenerateContentResponse }>\n generateContentStream(\n params: unknown,\n ): Promise<{\n stream: AsyncIterable<{ usageMetadata?: UsageMetadata | null }>\n response: Promise<GenerateContentResponse>\n }>\n model?: string\n}\n\ninterface GenAILike {\n getGenerativeModel(params: { model: string } & Record<string, unknown>): GenerativeModelLike\n}\n\n// ─── Public wrapper ───────────────────────────────────────────────────────────\n\n/**\n * Wraps a GoogleGenerativeAI client to transparently intercept\n * generateContent / generateContentStream calls and report token usage.\n *\n * Returns the same type T that was passed in.\n */\nexport function wrapGemini<T extends GenAILike>(client: T, tracker: Tracker): T {\n return new Proxy(client, {\n get(target, prop) {\n if (prop !== 'getGenerativeModel')\n return (target as Record<string | symbol, unknown>)[prop]\n\n return function (modelParams: { model: string } & Record<string, unknown>) {\n const modelInstance = target.getGenerativeModel(modelParams)\n const modelId = modelParams.model\n\n return new Proxy(modelInstance, {\n get(mTarget, mProp) {\n if (mProp === 'generateContent') {\n return async function (params: unknown) {\n let result: { response: GenerateContentResponse }\n try {\n result = await mTarget.generateContent(params)\n } catch (err) {\n throw err\n }\n\n const meta = result.response.usageMetadata\n tracker.track({\n model: modelId,\n inputTokens: meta?.promptTokenCount ?? 0,\n outputTokens: meta?.candidatesTokenCount ?? 0,\n })\n\n return result\n }\n }\n\n if (mProp === 'generateContentStream') {\n return async function (params: unknown) {\n let streamResult: {\n stream: AsyncIterable<{ usageMetadata?: UsageMetadata | null }>\n response: Promise<GenerateContentResponse>\n }\n try {\n streamResult = await mTarget.generateContentStream(params)\n } catch (err) {\n throw err\n }\n\n // Consume usage from the resolved response promise after streaming\n streamResult.response\n .then((res) => {\n const meta = res.usageMetadata\n tracker.track({\n model: modelId,\n inputTokens: meta?.promptTokenCount ?? 0,\n outputTokens: meta?.candidatesTokenCount ?? 0,\n })\n })\n .catch(() => {\n // best-effort\n })\n\n return streamResult\n }\n }\n\n return (mTarget as unknown as Record<string | symbol, unknown>)[mProp]\n },\n })\n }\n },\n }) as T\n}\n"],"mappings":"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","names":["bundledPrices","import_node_fs","import_node_os","import_node_path","extractMeta","extractUsage","trackWithMeta","wrapStream"]}
|
package/dist/index.js
CHANGED
|
@@ -172,18 +172,54 @@ var prices_default = {
|
|
|
172
172
|
updated_at: "2026-04-16",
|
|
173
173
|
source: "https://raw.githubusercontent.com/diogonzafe/tokenwatch/main/prices.json",
|
|
174
174
|
models: {
|
|
175
|
-
"gpt-4o": {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
"gpt-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
"
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
175
|
+
"gpt-4o": {
|
|
176
|
+
input: 2.5,
|
|
177
|
+
output: 10
|
|
178
|
+
},
|
|
179
|
+
"gpt-4o-mini": {
|
|
180
|
+
input: 0.15,
|
|
181
|
+
output: 0.6
|
|
182
|
+
},
|
|
183
|
+
"gpt-5": {
|
|
184
|
+
input: 1.25,
|
|
185
|
+
output: 10
|
|
186
|
+
},
|
|
187
|
+
"gpt-5-mini": {
|
|
188
|
+
input: 0.25,
|
|
189
|
+
output: 2
|
|
190
|
+
},
|
|
191
|
+
"gpt-5-nano": {
|
|
192
|
+
input: 0.05,
|
|
193
|
+
output: 0.4
|
|
194
|
+
},
|
|
195
|
+
"claude-opus-4-6": {
|
|
196
|
+
input: 5,
|
|
197
|
+
output: 25
|
|
198
|
+
},
|
|
199
|
+
"claude-sonnet-4-6": {
|
|
200
|
+
input: 3,
|
|
201
|
+
output: 15
|
|
202
|
+
},
|
|
203
|
+
"claude-haiku-4-5": {
|
|
204
|
+
input: 1,
|
|
205
|
+
output: 5
|
|
206
|
+
},
|
|
207
|
+
"gemini-2.5-pro": {
|
|
208
|
+
input: 1.25,
|
|
209
|
+
output: 10
|
|
210
|
+
},
|
|
211
|
+
"gemini-2.5-flash": {
|
|
212
|
+
input: 0.3,
|
|
213
|
+
output: 2.5
|
|
214
|
+
},
|
|
215
|
+
"deepseek-chat": {
|
|
216
|
+
input: 0.28,
|
|
217
|
+
output: 0.42
|
|
218
|
+
},
|
|
219
|
+
"deepseek-reasoner": {
|
|
220
|
+
input: 0.55,
|
|
221
|
+
output: 2.19
|
|
222
|
+
}
|
|
187
223
|
}
|
|
188
224
|
};
|
|
189
225
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/tracker.ts","../src/core/pricing.ts","../src/core/storage.ts","../src/core/sync.ts","../prices.json","../src/providers/openai.ts","../src/providers/anthropic.ts","../src/providers/gemini.ts"],"sourcesContent":["import { z } from 'zod'\nimport type {\n Tracker,\n TrackerConfig,\n UsageEntry,\n Report,\n ModelStats,\n SessionStats,\n UserStats,\n PriceMap,\n} from '../types/index.js'\nimport { resolvePrice, calculateCost } from './pricing.js'\nimport { createStorage } from './storage.js'\nimport { getRemotePrices } from './sync.js'\nimport bundledPricesFile from '../../prices.json' assert { type: 'json' }\n\nconst bundledPrices: PriceMap = bundledPricesFile.models as PriceMap\n\n// ─── Config validation schema ─────────────────────────────────────────────────\n\nconst ModelPriceSchema = z.object({\n input: z.number().nonnegative(),\n output: z.number().nonnegative(),\n})\n\nconst TrackerConfigSchema = z.object({\n storage: z.enum(['memory', 'sqlite']).optional().default('memory'),\n alertThreshold: z.number().positive().optional(),\n webhookUrl: z.string().url().optional(),\n syncPrices: z.boolean().optional().default(true),\n customPrices: z.record(z.string(), ModelPriceSchema).optional(),\n})\n\nexport function createTracker(config: TrackerConfig = {}): Tracker {\n const parsed = TrackerConfigSchema.safeParse(config)\n if (!parsed.success) {\n const issues = parsed.error.issues.map((i) => ` ${i.path.join('.')}: ${i.message}`).join('\\n')\n throw new Error(`[tokenwatch] Invalid config:\\n${issues}`)\n }\n\n const {\n storage: storageType,\n alertThreshold,\n webhookUrl,\n syncPrices,\n customPrices,\n } = parsed.data\n\n const storage = createStorage(storageType)\n\n // Fetch remote prices in the background — bundled prices are used as fallback\n // until the sync resolves. Zero latency added to createTracker().\n let remotePrices: PriceMap | undefined\n if (syncPrices) {\n getRemotePrices()\n .then((result) => {\n if (result) remotePrices = result\n })\n .catch(() => {\n // best-effort — bundled prices remain in use\n })\n }\n\n let alertFired = false\n const startedAt = new Date().toISOString()\n\n function resolveModelPrice(model: string) {\n return resolvePrice(model, {\n bundledPrices,\n ...(customPrices !== undefined && { customPrices }),\n ...(remotePrices !== undefined && { remotePrices }),\n })\n }\n\n function track(entry: Omit<UsageEntry, 'costUSD' | 'timestamp'>): void {\n const price = resolveModelPrice(entry.model)\n const costUSD = calculateCost(entry.inputTokens, entry.outputTokens, price)\n const full: UsageEntry = {\n ...entry,\n costUSD,\n timestamp: new Date().toISOString(),\n }\n storage.record(full)\n maybeFireAlert()\n }\n\n function maybeFireAlert(): void {\n if (!alertThreshold || !webhookUrl || alertFired) return\n const total = computeTotal(storage.getAll())\n if (total >= alertThreshold) {\n alertFired = true\n const payload = {\n text: `[tokenwatch] Alert: total cost reached $${total.toFixed(4)} USD (threshold: $${alertThreshold})`,\n }\n fetch(webhookUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }).catch(() => {\n // fire-and-forget\n })\n }\n }\n\n function getReport(): Report {\n const entries = storage.getAll()\n const byModel: Record<string, ModelStats> = {}\n const bySession: Record<string, SessionStats> = {}\n const byUser: Record<string, UserStats> = {}\n\n let totalInput = 0\n let totalOutput = 0\n let totalCost = 0\n let lastTimestamp = startedAt\n\n for (const e of entries) {\n totalInput += e.inputTokens\n totalOutput += e.outputTokens\n totalCost += e.costUSD\n if (e.timestamp > lastTimestamp) lastTimestamp = e.timestamp\n\n // byModel\n const m = (byModel[e.model] ??= { costUSD: 0, calls: 0, tokens: { input: 0, output: 0 } })\n m.costUSD += e.costUSD\n m.calls += 1\n m.tokens.input += e.inputTokens\n m.tokens.output += e.outputTokens\n\n // bySession\n if (e.sessionId) {\n const s = (bySession[e.sessionId] ??= { costUSD: 0, calls: 0 })\n s.costUSD += e.costUSD\n s.calls += 1\n }\n\n // byUser\n if (e.userId) {\n const u = (byUser[e.userId] ??= { costUSD: 0, calls: 0 })\n u.costUSD += e.costUSD\n u.calls += 1\n }\n }\n\n return {\n totalCostUSD: totalCost,\n totalTokens: { input: totalInput, output: totalOutput },\n byModel,\n bySession,\n byUser,\n period: { from: startedAt, to: lastTimestamp },\n }\n }\n\n function reset(): void {\n storage.clearAll()\n alertFired = false\n }\n\n function resetSession(sessionId: string): void {\n storage.clearSession(sessionId)\n }\n\n function exportJSON(): string {\n return JSON.stringify(getReport(), null, 2)\n }\n\n function exportCSV(): string {\n const entries = storage.getAll()\n const header = 'timestamp,model,inputTokens,outputTokens,costUSD,sessionId,userId'\n const rows = entries.map((e) =>\n [\n e.timestamp,\n e.model,\n e.inputTokens,\n e.outputTokens,\n e.costUSD.toFixed(8),\n e.sessionId ?? '',\n e.userId ?? '',\n ].join(','),\n )\n return [header, ...rows].join('\\n')\n }\n\n return { track, getReport, reset, resetSession, exportJSON, exportCSV }\n}\n\nfunction computeTotal(entries: UsageEntry[]): number {\n return entries.reduce((sum, e) => sum + e.costUSD, 0)\n}\n","import type { ModelPrice, PriceMap } from '../types/index.js'\n\n/**\n * Resolve price for a model using 3-layer priority:\n * 1. customPrices (user override)\n * 2. remotePrices (synced from GitHub, cached 24h)\n * 3. bundledPrices (always-present fallback)\n *\n * Falls back to zero-cost with a console warning when model is not found anywhere.\n */\nexport function resolvePrice(\n model: string,\n layers: {\n customPrices?: PriceMap\n remotePrices?: PriceMap\n bundledPrices: PriceMap\n },\n): ModelPrice {\n const { customPrices, remotePrices, bundledPrices } = layers\n\n const found =\n lookupInMap(model, customPrices) ??\n lookupInMap(model, remotePrices) ??\n lookupInMap(model, bundledPrices)\n\n if (found) return found\n\n console.warn(\n `[tokenwatch] Unknown model \"${model}\". Cost will be recorded as $0. ` +\n `Add it via customPrices or update prices with: tokenwatch sync`,\n )\n return { input: 0, output: 0 }\n}\n\n/**\n * Look up a model in a PriceMap using:\n * 1. exact key match\n * 2. prefix match — map key is a prefix of the model string (e.g. \"gpt-4o\" matches \"gpt-4o-2024-11-20\")\n * 3. reverse prefix — model string is a prefix of a map key (unusual, safety net)\n */\nfunction lookupInMap(model: string, map: PriceMap | undefined): ModelPrice | undefined {\n if (!map) return undefined\n\n if (model in map) return map[model]\n\n // prefix match\n for (const key of Object.keys(map)) {\n if (model.startsWith(key) || key.startsWith(model)) {\n return map[key]\n }\n }\n\n return undefined\n}\n\n/**\n * Calculate cost in USD given token counts and per-million-token prices.\n */\nexport function calculateCost(\n inputTokens: number,\n outputTokens: number,\n price: ModelPrice,\n): number {\n return (inputTokens / 1_000_000) * price.input + (outputTokens / 1_000_000) * price.output\n}\n","import { createRequire } from 'node:module'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport { mkdirSync } from 'node:fs'\nimport type { IStorage, UsageEntry } from '../types/index.js'\n\n// ─── Memory storage ───────────────────────────────────────────────────────────\n\nexport class MemoryStorage implements IStorage {\n private entries: UsageEntry[] = []\n\n record(entry: UsageEntry): void {\n this.entries.push(entry)\n }\n\n getAll(): UsageEntry[] {\n return [...this.entries]\n }\n\n clearAll(): void {\n this.entries = []\n }\n\n clearSession(sessionId: string): void {\n this.entries = this.entries.filter((e) => e.sessionId !== sessionId)\n }\n}\n\n// ─── SQLite storage ───────────────────────────────────────────────────────────\n\nconst DB_DIR = join(homedir(), '.tokenwatch')\nconst DB_PATH = join(DB_DIR, 'usage.db')\n\nexport class SqliteStorage implements IStorage {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private db: any\n\n constructor(dbPath = DB_PATH) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let BetterSqlite3: any\n try {\n // In CJS context globalThis.require is the native require; in ESM use createRequire.\n // This makes the lazy load work in both output formats produced by tsup.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const req: NodeRequire =\n typeof (globalThis as any).require === 'function'\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (globalThis as any).require\n : createRequire(import.meta.url)\n BetterSqlite3 = req('better-sqlite3')\n } catch {\n throw new Error(\n '[tokenwatch] SQLite storage requires better-sqlite3. ' +\n 'Run: npm install better-sqlite3',\n )\n }\n\n mkdirSync(DB_DIR, { recursive: true })\n this.db = new BetterSqlite3(dbPath)\n this.migrate()\n }\n\n private migrate(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS usage (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n model TEXT NOT NULL,\n input_tokens INTEGER NOT NULL,\n output_tokens INTEGER NOT NULL,\n cost_usd REAL NOT NULL,\n session_id TEXT,\n user_id TEXT,\n timestamp TEXT NOT NULL\n )\n `)\n }\n\n record(entry: UsageEntry): void {\n this.db\n .prepare(\n `INSERT INTO usage\n (model, input_tokens, output_tokens, cost_usd, session_id, user_id, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\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\n getAll(): UsageEntry[] {\n const rows = this.db.prepare('SELECT * FROM usage').all() as Array<{\n model: string\n input_tokens: number\n output_tokens: number\n cost_usd: number\n session_id: string | null\n user_id: string | null\n timestamp: string\n }>\n\n return rows.map((r) => ({\n model: r.model,\n inputTokens: r.input_tokens,\n outputTokens: r.output_tokens,\n costUSD: r.cost_usd,\n ...(r.session_id != null && { sessionId: r.session_id }),\n ...(r.user_id != null && { userId: r.user_id }),\n timestamp: r.timestamp,\n }))\n }\n\n clearAll(): void {\n this.db.exec('DELETE FROM usage')\n }\n\n clearSession(sessionId: string): void {\n this.db.prepare('DELETE FROM usage WHERE session_id = ?').run(sessionId)\n }\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\nexport function createStorage(type: 'memory' | 'sqlite'): IStorage {\n if (type === 'sqlite') return new SqliteStorage()\n return new MemoryStorage()\n}\n","import { readFile, writeFile, mkdir } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport type { PricesFile, PriceMap } from '../types/index.js'\n\nconst CACHE_DIR = join(homedir(), '.tokenwatch')\nconst CACHE_FILE = join(CACHE_DIR, 'prices.json')\nconst CACHE_TTL_MS = 24 * 60 * 60 * 1000 // 24 hours\nconst REMOTE_URL =\n 'https://raw.githubusercontent.com/diogonzafe/tokenwatch/main/prices.json'\n\nexport async function fetchRemotePrices(url = REMOTE_URL): Promise<PriceMap | null> {\n try {\n const res = await fetch(url, { signal: AbortSignal.timeout(8_000) })\n if (!res.ok) return null\n const data = (await res.json()) as PricesFile\n if (!data?.models) return null\n await persistCache(data)\n return data.models\n } catch {\n return null\n }\n}\n\nexport async function loadCachedPrices(): Promise<PriceMap | null> {\n if (!existsSync(CACHE_FILE)) return null\n try {\n const raw = await readFile(CACHE_FILE, 'utf8')\n const data = JSON.parse(raw) as PricesFile & { _cachedAt?: number }\n const age = Date.now() - (data._cachedAt ?? 0)\n if (age > CACHE_TTL_MS) return null\n return data.models ?? null\n } catch {\n return null\n }\n}\n\nasync function persistCache(data: PricesFile): Promise<void> {\n try {\n await mkdir(CACHE_DIR, { recursive: true })\n const payload = { ...data, _cachedAt: Date.now() }\n await writeFile(CACHE_FILE, JSON.stringify(payload, null, 2), 'utf8')\n } catch {\n // best-effort — never throw\n }\n}\n\n/**\n * Returns the best available remote price map:\n * 1. Valid local cache (< 24h)\n * 2. Fresh remote fetch (also updates cache)\n * 3. null if both fail\n */\nexport async function getRemotePrices(): Promise<PriceMap | null> {\n const cached = await loadCachedPrices()\n if (cached) return cached\n return fetchRemotePrices()\n}\n","{\n \"updated_at\": \"2026-04-16\",\n \"source\": \"https://raw.githubusercontent.com/diogonzafe/tokenwatch/main/prices.json\",\n \"models\": {\n \"gpt-4o\": { \"input\": 2.50, \"output\": 10.00 },\n \"gpt-4o-mini\": { \"input\": 0.15, \"output\": 0.60 },\n \"gpt-5\": { \"input\": 1.25, \"output\": 10.00 },\n \"gpt-5-mini\": { \"input\": 0.25, \"output\": 2.00 },\n \"gpt-5-nano\": { \"input\": 0.05, \"output\": 0.40 },\n \"claude-opus-4-6\": { \"input\": 5.00, \"output\": 25.00 },\n \"claude-sonnet-4-6\": { \"input\": 3.00, \"output\": 15.00 },\n \"claude-haiku-4-5\": { \"input\": 1.00, \"output\": 5.00 },\n \"gemini-2.5-pro\": { \"input\": 1.25, \"output\": 10.00 },\n \"gemini-2.5-flash\": { \"input\": 0.30, \"output\": 2.50 },\n \"deepseek-chat\": { \"input\": 0.28, \"output\": 0.42 },\n \"deepseek-reasoner\": { \"input\": 0.55, \"output\": 2.19 }\n }\n}\n","import type { Tracker, TrackingMeta } from '../types/index.js'\n\n// ─── Minimal structural types (no hard dep on openai package) ────────────────\n\ninterface Usage {\n prompt_tokens?: number\n completion_tokens?: number\n input_tokens?: number\n output_tokens?: number\n}\n\ninterface Completion {\n model?: string\n usage?: Usage | null\n}\n\ninterface StreamChunk {\n model?: string\n usage?: Usage | null\n}\n\ninterface CompletionsLike {\n create(params: Record<string, unknown>): Promise<unknown>\n}\n\ninterface ChatLike {\n completions: CompletionsLike\n}\n\ntype OpenAILike = { chat: ChatLike } & Record<string, unknown>\n\n// ─── Augmented return type ────────────────────────────────────────────────────\n// The wrapped client's create() accepts TrackingMeta fields (__sessionId, __userId)\n// in addition to the original params — no `as any` needed at the call site.\n\ntype AugmentedCreate<TCreate extends (...args: any[]) => any> = (\n params: Parameters<TCreate>[0] & TrackingMeta,\n) => ReturnType<TCreate>\n\ntype WrappedOpenAI<T extends OpenAILike> = Omit<T, 'chat'> & {\n chat: Omit<T['chat'], 'completions'> & {\n completions: Omit<T['chat']['completions'], 'create'> & {\n create: AugmentedCreate<T['chat']['completions']['create']>\n }\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction extractMeta(params: Record<string, unknown>): {\n cleaned: Record<string, unknown>\n sessionId: string | undefined\n userId: string | undefined\n} {\n const { __sessionId, __userId, ...cleaned } = params as Record<string, unknown> & TrackingMeta\n return {\n cleaned,\n sessionId: typeof __sessionId === 'string' ? __sessionId : undefined,\n userId: typeof __userId === 'string' ? __userId : undefined,\n }\n}\n\nfunction extractUsage(usage: Usage | null | undefined): {\n inputTokens: number\n outputTokens: number\n} {\n if (!usage) return { inputTokens: 0, outputTokens: 0 }\n return {\n inputTokens: usage.prompt_tokens ?? usage.input_tokens ?? 0,\n outputTokens: usage.completion_tokens ?? usage.output_tokens ?? 0,\n }\n}\n\nfunction trackWithMeta(\n tracker: Tracker,\n model: string,\n inputTokens: number,\n outputTokens: number,\n sessionId: string | undefined,\n userId: string | undefined,\n): void {\n tracker.track({\n model,\n inputTokens,\n outputTokens,\n ...(sessionId !== undefined && { sessionId }),\n ...(userId !== undefined && { userId }),\n })\n}\n\n// ─── Streaming wrapper ────────────────────────────────────────────────────────\n\nasync function* wrapStream(\n stream: AsyncIterable<StreamChunk>,\n model: string,\n sessionId: string | undefined,\n userId: string | undefined,\n tracker: Tracker,\n): AsyncGenerator<StreamChunk> {\n let lastChunk: StreamChunk | undefined\n for await (const chunk of stream) {\n lastChunk = chunk\n yield chunk\n }\n if (lastChunk?.usage) {\n const { inputTokens, outputTokens } = extractUsage(lastChunk.usage)\n trackWithMeta(tracker, model, inputTokens, outputTokens, sessionId, userId)\n }\n}\n\n// ─── Public wrapper ───────────────────────────────────────────────────────────\n\n/**\n * Wraps an OpenAI client (or any OpenAI-compatible client) to transparently\n * intercept chat.completions.create calls and report token usage to the tracker.\n *\n * The returned client is typed to accept __sessionId and __userId alongside the\n * normal params — no type cast required at the call site.\n */\nexport function wrapOpenAI<T extends OpenAILike>(client: T, tracker: Tracker): WrappedOpenAI<T> {\n const proxiedCompletions = new Proxy(client.chat.completions, {\n get(target, prop) {\n if (prop !== 'create')\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n\n return async function (params: Record<string, unknown>) {\n const { cleaned, sessionId, userId } = extractMeta(params)\n const model = typeof cleaned['model'] === 'string' ? cleaned['model'] : 'unknown'\n\n let result: unknown\n try {\n result = await (target as CompletionsLike).create(cleaned)\n } catch (err) {\n throw err\n }\n\n if (result && typeof result === 'object' && Symbol.asyncIterator in result) {\n return wrapStream(\n result as AsyncIterable<StreamChunk>,\n model,\n sessionId,\n userId,\n tracker,\n )\n }\n\n const completion = result as Completion\n const { inputTokens, outputTokens } = extractUsage(completion.usage)\n trackWithMeta(\n tracker,\n completion.model ?? model,\n inputTokens,\n outputTokens,\n sessionId,\n userId,\n )\n\n return result\n }\n },\n })\n\n const proxiedChat = new Proxy(client.chat, {\n get(target, prop) {\n if (prop === 'completions') return proxiedCompletions\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n },\n })\n\n return new Proxy(client, {\n get(target, prop) {\n if (prop === 'chat') return proxiedChat\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n },\n }) as unknown as WrappedOpenAI<T>\n}\n","import type { Tracker, TrackingMeta } from '../types/index.js'\n\n// ─── Minimal structural types ─────────────────────────────────────────────────\n\ninterface AnthropicUsage {\n input_tokens?: number\n output_tokens?: number\n}\n\ninterface AnthropicMessage {\n model?: string\n usage?: AnthropicUsage | null\n}\n\ninterface AnthropicStreamEvent {\n type?: string\n usage?: AnthropicUsage | null\n message?: AnthropicMessage\n}\n\ninterface MessagesLike {\n create(params: Record<string, unknown>): Promise<unknown>\n}\n\ntype AnthropicLike = { messages: MessagesLike } & Record<string, unknown>\n\n// ─── Augmented return type ────────────────────────────────────────────────────\n\ntype AugmentedCreate<TCreate extends (...args: any[]) => any> = (\n params: Parameters<TCreate>[0] & TrackingMeta,\n) => ReturnType<TCreate>\n\ntype WrappedAnthropic<T extends AnthropicLike> = Omit<T, 'messages'> & {\n messages: Omit<T['messages'], 'create'> & {\n create: AugmentedCreate<T['messages']['create']>\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction extractMeta(params: Record<string, unknown>): {\n cleaned: Record<string, unknown>\n sessionId: string | undefined\n userId: string | undefined\n} {\n const { __sessionId, __userId, ...cleaned } = params as Record<string, unknown> & TrackingMeta\n return {\n cleaned,\n sessionId: typeof __sessionId === 'string' ? __sessionId : undefined,\n userId: typeof __userId === 'string' ? __userId : undefined,\n }\n}\n\nfunction extractUsage(usage: AnthropicUsage | null | undefined): {\n inputTokens: number\n outputTokens: number\n} {\n if (!usage) return { inputTokens: 0, outputTokens: 0 }\n return {\n inputTokens: usage.input_tokens ?? 0,\n outputTokens: usage.output_tokens ?? 0,\n }\n}\n\nfunction trackWithMeta(\n tracker: Tracker,\n model: string,\n inputTokens: number,\n outputTokens: number,\n sessionId: string | undefined,\n userId: string | undefined,\n): void {\n tracker.track({\n model,\n inputTokens,\n outputTokens,\n ...(sessionId !== undefined && { sessionId }),\n ...(userId !== undefined && { userId }),\n })\n}\n\n// ─── Streaming wrapper ────────────────────────────────────────────────────────\n\nasync function* wrapStream(\n stream: AsyncIterable<AnthropicStreamEvent>,\n model: string,\n sessionId: string | undefined,\n userId: string | undefined,\n tracker: Tracker,\n): AsyncGenerator<AnthropicStreamEvent> {\n let inputTokens = 0\n let outputTokens = 0\n\n for await (const event of stream) {\n yield event\n\n if (event.type === 'message_start' && event.message?.usage) {\n inputTokens = event.message.usage.input_tokens ?? 0\n }\n if (event.type === 'message_delta' && event.usage) {\n outputTokens = event.usage.output_tokens ?? 0\n }\n }\n\n if (inputTokens > 0 || outputTokens > 0) {\n trackWithMeta(tracker, model, inputTokens, outputTokens, sessionId, userId)\n }\n}\n\n// ─── Public wrapper ───────────────────────────────────────────────────────────\n\n/**\n * Wraps an Anthropic client to transparently intercept messages.create calls\n * and report token usage to the tracker.\n *\n * The returned client is typed to accept __sessionId and __userId alongside the\n * normal params — no type cast required at the call site.\n */\nexport function wrapAnthropic<T extends AnthropicLike>(\n client: T,\n tracker: Tracker,\n): WrappedAnthropic<T> {\n const proxiedMessages = new Proxy(client.messages, {\n get(target, prop) {\n if (prop !== 'create')\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n\n return async function (params: Record<string, unknown>) {\n const { cleaned, sessionId, userId } = extractMeta(params)\n const model = typeof cleaned['model'] === 'string' ? cleaned['model'] : 'unknown'\n\n let result: unknown\n try {\n result = await (target as MessagesLike).create(cleaned)\n } catch (err) {\n throw err\n }\n\n if (result && typeof result === 'object' && Symbol.asyncIterator in result) {\n return wrapStream(\n result as AsyncIterable<AnthropicStreamEvent>,\n model,\n sessionId,\n userId,\n tracker,\n )\n }\n\n const message = result as AnthropicMessage\n const { inputTokens, outputTokens } = extractUsage(message.usage)\n trackWithMeta(\n tracker,\n message.model ?? model,\n inputTokens,\n outputTokens,\n sessionId,\n userId,\n )\n\n return result\n }\n },\n })\n\n return new Proxy(client, {\n get(target, prop) {\n if (prop === 'messages') return proxiedMessages\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n },\n }) as unknown as WrappedAnthropic<T>\n}\n","import type { Tracker } from '../types/index.js'\n\n// ─── Minimal structural types ─────────────────────────────────────────────────\n\ninterface UsageMetadata {\n promptTokenCount?: number\n candidatesTokenCount?: number\n totalTokenCount?: number\n}\n\ninterface GenerateContentResponse {\n usageMetadata?: UsageMetadata | null\n}\n\ninterface GenerativeModelLike {\n generateContent(params: unknown): Promise<{ response: GenerateContentResponse }>\n generateContentStream(\n params: unknown,\n ): Promise<{\n stream: AsyncIterable<{ usageMetadata?: UsageMetadata | null }>\n response: Promise<GenerateContentResponse>\n }>\n model?: string\n}\n\ninterface GenAILike {\n getGenerativeModel(params: { model: string } & Record<string, unknown>): GenerativeModelLike\n}\n\n// ─── Public wrapper ───────────────────────────────────────────────────────────\n\n/**\n * Wraps a GoogleGenerativeAI client to transparently intercept\n * generateContent / generateContentStream calls and report token usage.\n *\n * Returns the same type T that was passed in.\n */\nexport function wrapGemini<T extends GenAILike>(client: T, tracker: Tracker): T {\n return new Proxy(client, {\n get(target, prop) {\n if (prop !== 'getGenerativeModel')\n return (target as Record<string | symbol, unknown>)[prop]\n\n return function (modelParams: { model: string } & Record<string, unknown>) {\n const modelInstance = target.getGenerativeModel(modelParams)\n const modelId = modelParams.model\n\n return new Proxy(modelInstance, {\n get(mTarget, mProp) {\n if (mProp === 'generateContent') {\n return async function (params: unknown) {\n let result: { response: GenerateContentResponse }\n try {\n result = await mTarget.generateContent(params)\n } catch (err) {\n throw err\n }\n\n const meta = result.response.usageMetadata\n tracker.track({\n model: modelId,\n inputTokens: meta?.promptTokenCount ?? 0,\n outputTokens: meta?.candidatesTokenCount ?? 0,\n })\n\n return result\n }\n }\n\n if (mProp === 'generateContentStream') {\n return async function (params: unknown) {\n let streamResult: {\n stream: AsyncIterable<{ usageMetadata?: UsageMetadata | null }>\n response: Promise<GenerateContentResponse>\n }\n try {\n streamResult = await mTarget.generateContentStream(params)\n } catch (err) {\n throw err\n }\n\n // Consume usage from the resolved response promise after streaming\n streamResult.response\n .then((res) => {\n const meta = res.usageMetadata\n tracker.track({\n model: modelId,\n inputTokens: meta?.promptTokenCount ?? 0,\n outputTokens: meta?.candidatesTokenCount ?? 0,\n })\n })\n .catch(() => {\n // best-effort\n })\n\n return streamResult\n }\n }\n\n return (mTarget as unknown as Record<string | symbol, unknown>)[mProp]\n },\n })\n }\n },\n }) as T\n}\n"],"mappings":"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","names":["bundledPrices","homedir","join","extractMeta","extractUsage","trackWithMeta","wrapStream"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/tracker.ts","../src/core/pricing.ts","../src/core/storage.ts","../src/core/sync.ts","../prices.json","../src/providers/openai.ts","../src/providers/anthropic.ts","../src/providers/gemini.ts"],"sourcesContent":["import { z } from 'zod'\nimport type {\n Tracker,\n TrackerConfig,\n UsageEntry,\n Report,\n ModelStats,\n SessionStats,\n UserStats,\n PriceMap,\n} from '../types/index.js'\nimport { resolvePrice, calculateCost } from './pricing.js'\nimport { createStorage } from './storage.js'\nimport { getRemotePrices } from './sync.js'\nimport bundledPricesFile from '../../prices.json' assert { type: 'json' }\n\nconst bundledPrices: PriceMap = bundledPricesFile.models as PriceMap\n\n// ─── Config validation schema ─────────────────────────────────────────────────\n\nconst ModelPriceSchema = z.object({\n input: z.number().nonnegative(),\n output: z.number().nonnegative(),\n})\n\nconst TrackerConfigSchema = z.object({\n storage: z.enum(['memory', 'sqlite']).optional().default('memory'),\n alertThreshold: z.number().positive().optional(),\n webhookUrl: z.string().url().optional(),\n syncPrices: z.boolean().optional().default(true),\n customPrices: z.record(z.string(), ModelPriceSchema).optional(),\n})\n\nexport function createTracker(config: TrackerConfig = {}): Tracker {\n const parsed = TrackerConfigSchema.safeParse(config)\n if (!parsed.success) {\n const issues = parsed.error.issues.map((i) => ` ${i.path.join('.')}: ${i.message}`).join('\\n')\n throw new Error(`[tokenwatch] Invalid config:\\n${issues}`)\n }\n\n const {\n storage: storageType,\n alertThreshold,\n webhookUrl,\n syncPrices,\n customPrices,\n } = parsed.data\n\n const storage = createStorage(storageType)\n\n // Fetch remote prices in the background — bundled prices are used as fallback\n // until the sync resolves. Zero latency added to createTracker().\n let remotePrices: PriceMap | undefined\n if (syncPrices) {\n getRemotePrices()\n .then((result) => {\n if (result) remotePrices = result\n })\n .catch(() => {\n // best-effort — bundled prices remain in use\n })\n }\n\n let alertFired = false\n const startedAt = new Date().toISOString()\n\n function resolveModelPrice(model: string) {\n return resolvePrice(model, {\n bundledPrices,\n ...(customPrices !== undefined && { customPrices }),\n ...(remotePrices !== undefined && { remotePrices }),\n })\n }\n\n function track(entry: Omit<UsageEntry, 'costUSD' | 'timestamp'>): void {\n const price = resolveModelPrice(entry.model)\n const costUSD = calculateCost(entry.inputTokens, entry.outputTokens, price)\n const full: UsageEntry = {\n ...entry,\n costUSD,\n timestamp: new Date().toISOString(),\n }\n storage.record(full)\n maybeFireAlert()\n }\n\n function maybeFireAlert(): void {\n if (!alertThreshold || !webhookUrl || alertFired) return\n const total = computeTotal(storage.getAll())\n if (total >= alertThreshold) {\n alertFired = true\n const payload = {\n text: `[tokenwatch] Alert: total cost reached $${total.toFixed(4)} USD (threshold: $${alertThreshold})`,\n }\n fetch(webhookUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }).catch(() => {\n // fire-and-forget\n })\n }\n }\n\n function getReport(): Report {\n const entries = storage.getAll()\n const byModel: Record<string, ModelStats> = {}\n const bySession: Record<string, SessionStats> = {}\n const byUser: Record<string, UserStats> = {}\n\n let totalInput = 0\n let totalOutput = 0\n let totalCost = 0\n let lastTimestamp = startedAt\n\n for (const e of entries) {\n totalInput += e.inputTokens\n totalOutput += e.outputTokens\n totalCost += e.costUSD\n if (e.timestamp > lastTimestamp) lastTimestamp = e.timestamp\n\n // byModel\n const m = (byModel[e.model] ??= { costUSD: 0, calls: 0, tokens: { input: 0, output: 0 } })\n m.costUSD += e.costUSD\n m.calls += 1\n m.tokens.input += e.inputTokens\n m.tokens.output += e.outputTokens\n\n // bySession\n if (e.sessionId) {\n const s = (bySession[e.sessionId] ??= { costUSD: 0, calls: 0 })\n s.costUSD += e.costUSD\n s.calls += 1\n }\n\n // byUser\n if (e.userId) {\n const u = (byUser[e.userId] ??= { costUSD: 0, calls: 0 })\n u.costUSD += e.costUSD\n u.calls += 1\n }\n }\n\n return {\n totalCostUSD: totalCost,\n totalTokens: { input: totalInput, output: totalOutput },\n byModel,\n bySession,\n byUser,\n period: { from: startedAt, to: lastTimestamp },\n }\n }\n\n function reset(): void {\n storage.clearAll()\n alertFired = false\n }\n\n function resetSession(sessionId: string): void {\n storage.clearSession(sessionId)\n }\n\n function exportJSON(): string {\n return JSON.stringify(getReport(), null, 2)\n }\n\n function exportCSV(): string {\n const entries = storage.getAll()\n const header = 'timestamp,model,inputTokens,outputTokens,costUSD,sessionId,userId'\n const rows = entries.map((e) =>\n [\n e.timestamp,\n e.model,\n e.inputTokens,\n e.outputTokens,\n e.costUSD.toFixed(8),\n e.sessionId ?? '',\n e.userId ?? '',\n ].join(','),\n )\n return [header, ...rows].join('\\n')\n }\n\n return { track, getReport, reset, resetSession, exportJSON, exportCSV }\n}\n\nfunction computeTotal(entries: UsageEntry[]): number {\n return entries.reduce((sum, e) => sum + e.costUSD, 0)\n}\n","import type { ModelPrice, PriceMap } from '../types/index.js'\n\n/**\n * Resolve price for a model using 3-layer priority:\n * 1. customPrices (user override)\n * 2. remotePrices (synced from GitHub, cached 24h)\n * 3. bundledPrices (always-present fallback)\n *\n * Falls back to zero-cost with a console warning when model is not found anywhere.\n */\nexport function resolvePrice(\n model: string,\n layers: {\n customPrices?: PriceMap\n remotePrices?: PriceMap\n bundledPrices: PriceMap\n },\n): ModelPrice {\n const { customPrices, remotePrices, bundledPrices } = layers\n\n const found =\n lookupInMap(model, customPrices) ??\n lookupInMap(model, remotePrices) ??\n lookupInMap(model, bundledPrices)\n\n if (found) return found\n\n console.warn(\n `[tokenwatch] Unknown model \"${model}\". Cost will be recorded as $0. ` +\n `Add it via customPrices or update prices with: tokenwatch sync`,\n )\n return { input: 0, output: 0 }\n}\n\n/**\n * Look up a model in a PriceMap using:\n * 1. exact key match\n * 2. prefix match — map key is a prefix of the model string (e.g. \"gpt-4o\" matches \"gpt-4o-2024-11-20\")\n * 3. reverse prefix — model string is a prefix of a map key (unusual, safety net)\n */\nfunction lookupInMap(model: string, map: PriceMap | undefined): ModelPrice | undefined {\n if (!map) return undefined\n\n if (model in map) return map[model]\n\n // prefix match\n for (const key of Object.keys(map)) {\n if (model.startsWith(key) || key.startsWith(model)) {\n return map[key]\n }\n }\n\n return undefined\n}\n\n/**\n * Calculate cost in USD given token counts and per-million-token prices.\n */\nexport function calculateCost(\n inputTokens: number,\n outputTokens: number,\n price: ModelPrice,\n): number {\n return (inputTokens / 1_000_000) * price.input + (outputTokens / 1_000_000) * price.output\n}\n","import { createRequire } from 'node:module'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport { mkdirSync } from 'node:fs'\nimport type { IStorage, UsageEntry } from '../types/index.js'\n\n// ─── Memory storage ───────────────────────────────────────────────────────────\n\nexport class MemoryStorage implements IStorage {\n private entries: UsageEntry[] = []\n\n record(entry: UsageEntry): void {\n this.entries.push(entry)\n }\n\n getAll(): UsageEntry[] {\n return [...this.entries]\n }\n\n clearAll(): void {\n this.entries = []\n }\n\n clearSession(sessionId: string): void {\n this.entries = this.entries.filter((e) => e.sessionId !== sessionId)\n }\n}\n\n// ─── SQLite storage ───────────────────────────────────────────────────────────\n\nconst DB_DIR = join(homedir(), '.tokenwatch')\nconst DB_PATH = join(DB_DIR, 'usage.db')\n\nexport class SqliteStorage implements IStorage {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private db: any\n\n constructor(dbPath = DB_PATH) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let BetterSqlite3: any\n try {\n // In CJS context globalThis.require is the native require; in ESM use createRequire.\n // This makes the lazy load work in both output formats produced by tsup.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const req: NodeRequire =\n typeof (globalThis as any).require === 'function'\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (globalThis as any).require\n : createRequire(import.meta.url)\n BetterSqlite3 = req('better-sqlite3')\n } catch {\n throw new Error(\n '[tokenwatch] SQLite storage requires better-sqlite3. ' +\n 'Run: npm install better-sqlite3',\n )\n }\n\n mkdirSync(DB_DIR, { recursive: true })\n this.db = new BetterSqlite3(dbPath)\n this.migrate()\n }\n\n private migrate(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS usage (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n model TEXT NOT NULL,\n input_tokens INTEGER NOT NULL,\n output_tokens INTEGER NOT NULL,\n cost_usd REAL NOT NULL,\n session_id TEXT,\n user_id TEXT,\n timestamp TEXT NOT NULL\n )\n `)\n }\n\n record(entry: UsageEntry): void {\n this.db\n .prepare(\n `INSERT INTO usage\n (model, input_tokens, output_tokens, cost_usd, session_id, user_id, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\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\n getAll(): UsageEntry[] {\n const rows = this.db.prepare('SELECT * FROM usage').all() as Array<{\n model: string\n input_tokens: number\n output_tokens: number\n cost_usd: number\n session_id: string | null\n user_id: string | null\n timestamp: string\n }>\n\n return rows.map((r) => ({\n model: r.model,\n inputTokens: r.input_tokens,\n outputTokens: r.output_tokens,\n costUSD: r.cost_usd,\n ...(r.session_id != null && { sessionId: r.session_id }),\n ...(r.user_id != null && { userId: r.user_id }),\n timestamp: r.timestamp,\n }))\n }\n\n clearAll(): void {\n this.db.exec('DELETE FROM usage')\n }\n\n clearSession(sessionId: string): void {\n this.db.prepare('DELETE FROM usage WHERE session_id = ?').run(sessionId)\n }\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\nexport function createStorage(type: 'memory' | 'sqlite'): IStorage {\n if (type === 'sqlite') return new SqliteStorage()\n return new MemoryStorage()\n}\n","import { readFile, writeFile, mkdir } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport type { PricesFile, PriceMap } from '../types/index.js'\n\nconst CACHE_DIR = join(homedir(), '.tokenwatch')\nconst CACHE_FILE = join(CACHE_DIR, 'prices.json')\nconst CACHE_TTL_MS = 24 * 60 * 60 * 1000 // 24 hours\nconst REMOTE_URL =\n 'https://raw.githubusercontent.com/diogonzafe/tokenwatch/main/prices.json'\n\nexport async function fetchRemotePrices(url = REMOTE_URL): Promise<PriceMap | null> {\n try {\n const res = await fetch(url, { signal: AbortSignal.timeout(8_000) })\n if (!res.ok) return null\n const data = (await res.json()) as PricesFile\n if (!data?.models) return null\n await persistCache(data)\n return data.models\n } catch {\n return null\n }\n}\n\nexport async function loadCachedPrices(): Promise<PriceMap | null> {\n if (!existsSync(CACHE_FILE)) return null\n try {\n const raw = await readFile(CACHE_FILE, 'utf8')\n const data = JSON.parse(raw) as PricesFile & { _cachedAt?: number }\n const age = Date.now() - (data._cachedAt ?? 0)\n if (age > CACHE_TTL_MS) return null\n return data.models ?? null\n } catch {\n return null\n }\n}\n\nasync function persistCache(data: PricesFile): Promise<void> {\n try {\n await mkdir(CACHE_DIR, { recursive: true })\n const payload = { ...data, _cachedAt: Date.now() }\n await writeFile(CACHE_FILE, JSON.stringify(payload, null, 2), 'utf8')\n } catch {\n // best-effort — never throw\n }\n}\n\n/**\n * Returns the best available remote price map:\n * 1. Valid local cache (< 24h)\n * 2. Fresh remote fetch (also updates cache)\n * 3. null if both fail\n */\nexport async function getRemotePrices(): Promise<PriceMap | null> {\n const cached = await loadCachedPrices()\n if (cached) return cached\n return fetchRemotePrices()\n}\n","{\n \"updated_at\": \"2026-04-16\",\n \"source\": \"https://raw.githubusercontent.com/diogonzafe/tokenwatch/main/prices.json\",\n \"models\": {\n \"gpt-4o\": {\n \"input\": 2.5,\n \"output\": 10\n },\n \"gpt-4o-mini\": {\n \"input\": 0.15,\n \"output\": 0.6\n },\n \"gpt-5\": {\n \"input\": 1.25,\n \"output\": 10\n },\n \"gpt-5-mini\": {\n \"input\": 0.25,\n \"output\": 2\n },\n \"gpt-5-nano\": {\n \"input\": 0.05,\n \"output\": 0.4\n },\n \"claude-opus-4-6\": {\n \"input\": 5,\n \"output\": 25\n },\n \"claude-sonnet-4-6\": {\n \"input\": 3,\n \"output\": 15\n },\n \"claude-haiku-4-5\": {\n \"input\": 1,\n \"output\": 5\n },\n \"gemini-2.5-pro\": {\n \"input\": 1.25,\n \"output\": 10\n },\n \"gemini-2.5-flash\": {\n \"input\": 0.3,\n \"output\": 2.5\n },\n \"deepseek-chat\": {\n \"input\": 0.28,\n \"output\": 0.42\n },\n \"deepseek-reasoner\": {\n \"input\": 0.55,\n \"output\": 2.19\n }\n }\n}\n","import type { Tracker, TrackingMeta } from '../types/index.js'\n\n// ─── Minimal structural types (no hard dep on openai package) ────────────────\n\ninterface Usage {\n prompt_tokens?: number\n completion_tokens?: number\n input_tokens?: number\n output_tokens?: number\n}\n\ninterface Completion {\n model?: string\n usage?: Usage | null\n}\n\ninterface StreamChunk {\n model?: string\n usage?: Usage | null\n}\n\ninterface CompletionsLike {\n create(params: Record<string, unknown>): Promise<unknown>\n}\n\ninterface ChatLike {\n completions: CompletionsLike\n}\n\ntype OpenAILike = { chat: ChatLike } & Record<string, unknown>\n\n// ─── Augmented return type ────────────────────────────────────────────────────\n// The wrapped client's create() accepts TrackingMeta fields (__sessionId, __userId)\n// in addition to the original params — no `as any` needed at the call site.\n\ntype AugmentedCreate<TCreate extends (...args: any[]) => any> = (\n params: Parameters<TCreate>[0] & TrackingMeta,\n) => ReturnType<TCreate>\n\ntype WrappedOpenAI<T extends OpenAILike> = Omit<T, 'chat'> & {\n chat: Omit<T['chat'], 'completions'> & {\n completions: Omit<T['chat']['completions'], 'create'> & {\n create: AugmentedCreate<T['chat']['completions']['create']>\n }\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction extractMeta(params: Record<string, unknown>): {\n cleaned: Record<string, unknown>\n sessionId: string | undefined\n userId: string | undefined\n} {\n const { __sessionId, __userId, ...cleaned } = params as Record<string, unknown> & TrackingMeta\n return {\n cleaned,\n sessionId: typeof __sessionId === 'string' ? __sessionId : undefined,\n userId: typeof __userId === 'string' ? __userId : undefined,\n }\n}\n\nfunction extractUsage(usage: Usage | null | undefined): {\n inputTokens: number\n outputTokens: number\n} {\n if (!usage) return { inputTokens: 0, outputTokens: 0 }\n return {\n inputTokens: usage.prompt_tokens ?? usage.input_tokens ?? 0,\n outputTokens: usage.completion_tokens ?? usage.output_tokens ?? 0,\n }\n}\n\nfunction trackWithMeta(\n tracker: Tracker,\n model: string,\n inputTokens: number,\n outputTokens: number,\n sessionId: string | undefined,\n userId: string | undefined,\n): void {\n tracker.track({\n model,\n inputTokens,\n outputTokens,\n ...(sessionId !== undefined && { sessionId }),\n ...(userId !== undefined && { userId }),\n })\n}\n\n// ─── Streaming wrapper ────────────────────────────────────────────────────────\n\nasync function* wrapStream(\n stream: AsyncIterable<StreamChunk>,\n model: string,\n sessionId: string | undefined,\n userId: string | undefined,\n tracker: Tracker,\n): AsyncGenerator<StreamChunk> {\n let lastChunk: StreamChunk | undefined\n for await (const chunk of stream) {\n lastChunk = chunk\n yield chunk\n }\n if (lastChunk?.usage) {\n const { inputTokens, outputTokens } = extractUsage(lastChunk.usage)\n trackWithMeta(tracker, model, inputTokens, outputTokens, sessionId, userId)\n }\n}\n\n// ─── Public wrapper ───────────────────────────────────────────────────────────\n\n/**\n * Wraps an OpenAI client (or any OpenAI-compatible client) to transparently\n * intercept chat.completions.create calls and report token usage to the tracker.\n *\n * The returned client is typed to accept __sessionId and __userId alongside the\n * normal params — no type cast required at the call site.\n */\nexport function wrapOpenAI<T extends OpenAILike>(client: T, tracker: Tracker): WrappedOpenAI<T> {\n const proxiedCompletions = new Proxy(client.chat.completions, {\n get(target, prop) {\n if (prop !== 'create')\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n\n return async function (params: Record<string, unknown>) {\n const { cleaned, sessionId, userId } = extractMeta(params)\n const model = typeof cleaned['model'] === 'string' ? cleaned['model'] : 'unknown'\n\n let result: unknown\n try {\n result = await (target as CompletionsLike).create(cleaned)\n } catch (err) {\n throw err\n }\n\n if (result && typeof result === 'object' && Symbol.asyncIterator in result) {\n return wrapStream(\n result as AsyncIterable<StreamChunk>,\n model,\n sessionId,\n userId,\n tracker,\n )\n }\n\n const completion = result as Completion\n const { inputTokens, outputTokens } = extractUsage(completion.usage)\n trackWithMeta(\n tracker,\n completion.model ?? model,\n inputTokens,\n outputTokens,\n sessionId,\n userId,\n )\n\n return result\n }\n },\n })\n\n const proxiedChat = new Proxy(client.chat, {\n get(target, prop) {\n if (prop === 'completions') return proxiedCompletions\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n },\n })\n\n return new Proxy(client, {\n get(target, prop) {\n if (prop === 'chat') return proxiedChat\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n },\n }) as unknown as WrappedOpenAI<T>\n}\n","import type { Tracker, TrackingMeta } from '../types/index.js'\n\n// ─── Minimal structural types ─────────────────────────────────────────────────\n\ninterface AnthropicUsage {\n input_tokens?: number\n output_tokens?: number\n}\n\ninterface AnthropicMessage {\n model?: string\n usage?: AnthropicUsage | null\n}\n\ninterface AnthropicStreamEvent {\n type?: string\n usage?: AnthropicUsage | null\n message?: AnthropicMessage\n}\n\ninterface MessagesLike {\n create(params: Record<string, unknown>): Promise<unknown>\n}\n\ntype AnthropicLike = { messages: MessagesLike } & Record<string, unknown>\n\n// ─── Augmented return type ────────────────────────────────────────────────────\n\ntype AugmentedCreate<TCreate extends (...args: any[]) => any> = (\n params: Parameters<TCreate>[0] & TrackingMeta,\n) => ReturnType<TCreate>\n\ntype WrappedAnthropic<T extends AnthropicLike> = Omit<T, 'messages'> & {\n messages: Omit<T['messages'], 'create'> & {\n create: AugmentedCreate<T['messages']['create']>\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction extractMeta(params: Record<string, unknown>): {\n cleaned: Record<string, unknown>\n sessionId: string | undefined\n userId: string | undefined\n} {\n const { __sessionId, __userId, ...cleaned } = params as Record<string, unknown> & TrackingMeta\n return {\n cleaned,\n sessionId: typeof __sessionId === 'string' ? __sessionId : undefined,\n userId: typeof __userId === 'string' ? __userId : undefined,\n }\n}\n\nfunction extractUsage(usage: AnthropicUsage | null | undefined): {\n inputTokens: number\n outputTokens: number\n} {\n if (!usage) return { inputTokens: 0, outputTokens: 0 }\n return {\n inputTokens: usage.input_tokens ?? 0,\n outputTokens: usage.output_tokens ?? 0,\n }\n}\n\nfunction trackWithMeta(\n tracker: Tracker,\n model: string,\n inputTokens: number,\n outputTokens: number,\n sessionId: string | undefined,\n userId: string | undefined,\n): void {\n tracker.track({\n model,\n inputTokens,\n outputTokens,\n ...(sessionId !== undefined && { sessionId }),\n ...(userId !== undefined && { userId }),\n })\n}\n\n// ─── Streaming wrapper ────────────────────────────────────────────────────────\n\nasync function* wrapStream(\n stream: AsyncIterable<AnthropicStreamEvent>,\n model: string,\n sessionId: string | undefined,\n userId: string | undefined,\n tracker: Tracker,\n): AsyncGenerator<AnthropicStreamEvent> {\n let inputTokens = 0\n let outputTokens = 0\n\n for await (const event of stream) {\n yield event\n\n if (event.type === 'message_start' && event.message?.usage) {\n inputTokens = event.message.usage.input_tokens ?? 0\n }\n if (event.type === 'message_delta' && event.usage) {\n outputTokens = event.usage.output_tokens ?? 0\n }\n }\n\n if (inputTokens > 0 || outputTokens > 0) {\n trackWithMeta(tracker, model, inputTokens, outputTokens, sessionId, userId)\n }\n}\n\n// ─── Public wrapper ───────────────────────────────────────────────────────────\n\n/**\n * Wraps an Anthropic client to transparently intercept messages.create calls\n * and report token usage to the tracker.\n *\n * The returned client is typed to accept __sessionId and __userId alongside the\n * normal params — no type cast required at the call site.\n */\nexport function wrapAnthropic<T extends AnthropicLike>(\n client: T,\n tracker: Tracker,\n): WrappedAnthropic<T> {\n const proxiedMessages = new Proxy(client.messages, {\n get(target, prop) {\n if (prop !== 'create')\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n\n return async function (params: Record<string, unknown>) {\n const { cleaned, sessionId, userId } = extractMeta(params)\n const model = typeof cleaned['model'] === 'string' ? cleaned['model'] : 'unknown'\n\n let result: unknown\n try {\n result = await (target as MessagesLike).create(cleaned)\n } catch (err) {\n throw err\n }\n\n if (result && typeof result === 'object' && Symbol.asyncIterator in result) {\n return wrapStream(\n result as AsyncIterable<AnthropicStreamEvent>,\n model,\n sessionId,\n userId,\n tracker,\n )\n }\n\n const message = result as AnthropicMessage\n const { inputTokens, outputTokens } = extractUsage(message.usage)\n trackWithMeta(\n tracker,\n message.model ?? model,\n inputTokens,\n outputTokens,\n sessionId,\n userId,\n )\n\n return result\n }\n },\n })\n\n return new Proxy(client, {\n get(target, prop) {\n if (prop === 'messages') return proxiedMessages\n return (target as unknown as Record<string | symbol, unknown>)[prop]\n },\n }) as unknown as WrappedAnthropic<T>\n}\n","import type { Tracker } from '../types/index.js'\n\n// ─── Minimal structural types ─────────────────────────────────────────────────\n\ninterface UsageMetadata {\n promptTokenCount?: number\n candidatesTokenCount?: number\n totalTokenCount?: number\n}\n\ninterface GenerateContentResponse {\n usageMetadata?: UsageMetadata | null\n}\n\ninterface GenerativeModelLike {\n generateContent(params: unknown): Promise<{ response: GenerateContentResponse }>\n generateContentStream(\n params: unknown,\n ): Promise<{\n stream: AsyncIterable<{ usageMetadata?: UsageMetadata | null }>\n response: Promise<GenerateContentResponse>\n }>\n model?: string\n}\n\ninterface GenAILike {\n getGenerativeModel(params: { model: string } & Record<string, unknown>): GenerativeModelLike\n}\n\n// ─── Public wrapper ───────────────────────────────────────────────────────────\n\n/**\n * Wraps a GoogleGenerativeAI client to transparently intercept\n * generateContent / generateContentStream calls and report token usage.\n *\n * Returns the same type T that was passed in.\n */\nexport function wrapGemini<T extends GenAILike>(client: T, tracker: Tracker): T {\n return new Proxy(client, {\n get(target, prop) {\n if (prop !== 'getGenerativeModel')\n return (target as Record<string | symbol, unknown>)[prop]\n\n return function (modelParams: { model: string } & Record<string, unknown>) {\n const modelInstance = target.getGenerativeModel(modelParams)\n const modelId = modelParams.model\n\n return new Proxy(modelInstance, {\n get(mTarget, mProp) {\n if (mProp === 'generateContent') {\n return async function (params: unknown) {\n let result: { response: GenerateContentResponse }\n try {\n result = await mTarget.generateContent(params)\n } catch (err) {\n throw err\n }\n\n const meta = result.response.usageMetadata\n tracker.track({\n model: modelId,\n inputTokens: meta?.promptTokenCount ?? 0,\n outputTokens: meta?.candidatesTokenCount ?? 0,\n })\n\n return result\n }\n }\n\n if (mProp === 'generateContentStream') {\n return async function (params: unknown) {\n let streamResult: {\n stream: AsyncIterable<{ usageMetadata?: UsageMetadata | null }>\n response: Promise<GenerateContentResponse>\n }\n try {\n streamResult = await mTarget.generateContentStream(params)\n } catch (err) {\n throw err\n }\n\n // Consume usage from the resolved response promise after streaming\n streamResult.response\n .then((res) => {\n const meta = res.usageMetadata\n tracker.track({\n model: modelId,\n inputTokens: meta?.promptTokenCount ?? 0,\n outputTokens: meta?.candidatesTokenCount ?? 0,\n })\n })\n .catch(() => {\n // best-effort\n })\n\n return streamResult\n }\n }\n\n return (mTarget as unknown as Record<string | symbol, unknown>)[mProp]\n },\n })\n }\n },\n }) as T\n}\n"],"mappings":"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","names":["bundledPrices","homedir","join","extractMeta","extractUsage","trackWithMeta","wrapStream"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diogonzafe/tokenwatch",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Transparent wrapper to track LLM API costs in real-time by session, user and model",
|
|
5
5
|
"author": "diogonzafe",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/better-sqlite3": "^7.6.12",
|
|
38
38
|
"@types/node": "^20.17.0",
|
|
39
|
+
"playwright": "^1.59.1",
|
|
39
40
|
"tsup": "^8.3.5",
|
|
40
41
|
"typescript": "^5.7.2",
|
|
41
42
|
"vitest": "^2.1.8"
|
|
@@ -47,10 +48,18 @@
|
|
|
47
48
|
"openai": ">=4.68.0"
|
|
48
49
|
},
|
|
49
50
|
"peerDependenciesMeta": {
|
|
50
|
-
"openai": {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
51
|
+
"openai": {
|
|
52
|
+
"optional": true
|
|
53
|
+
},
|
|
54
|
+
"@anthropic-ai/sdk": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"@google/generative-ai": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"better-sqlite3": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
54
63
|
},
|
|
55
64
|
"keywords": [
|
|
56
65
|
"llm",
|
package/prices.json
CHANGED
|
@@ -2,17 +2,53 @@
|
|
|
2
2
|
"updated_at": "2026-04-16",
|
|
3
3
|
"source": "https://raw.githubusercontent.com/diogonzafe/tokenwatch/main/prices.json",
|
|
4
4
|
"models": {
|
|
5
|
-
"gpt-4o":
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"gpt-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
5
|
+
"gpt-4o": {
|
|
6
|
+
"input": 2.5,
|
|
7
|
+
"output": 10
|
|
8
|
+
},
|
|
9
|
+
"gpt-4o-mini": {
|
|
10
|
+
"input": 0.15,
|
|
11
|
+
"output": 0.6
|
|
12
|
+
},
|
|
13
|
+
"gpt-5": {
|
|
14
|
+
"input": 1.25,
|
|
15
|
+
"output": 10
|
|
16
|
+
},
|
|
17
|
+
"gpt-5-mini": {
|
|
18
|
+
"input": 0.25,
|
|
19
|
+
"output": 2
|
|
20
|
+
},
|
|
21
|
+
"gpt-5-nano": {
|
|
22
|
+
"input": 0.05,
|
|
23
|
+
"output": 0.4
|
|
24
|
+
},
|
|
25
|
+
"claude-opus-4-6": {
|
|
26
|
+
"input": 5,
|
|
27
|
+
"output": 25
|
|
28
|
+
},
|
|
29
|
+
"claude-sonnet-4-6": {
|
|
30
|
+
"input": 3,
|
|
31
|
+
"output": 15
|
|
32
|
+
},
|
|
33
|
+
"claude-haiku-4-5": {
|
|
34
|
+
"input": 1,
|
|
35
|
+
"output": 5
|
|
36
|
+
},
|
|
37
|
+
"gemini-2.5-pro": {
|
|
38
|
+
"input": 1.25,
|
|
39
|
+
"output": 10
|
|
40
|
+
},
|
|
41
|
+
"gemini-2.5-flash": {
|
|
42
|
+
"input": 0.3,
|
|
43
|
+
"output": 2.5
|
|
44
|
+
},
|
|
45
|
+
"deepseek-chat": {
|
|
46
|
+
"input": 0.28,
|
|
47
|
+
"output": 0.42
|
|
48
|
+
},
|
|
49
|
+
"deepseek-reasoner": {
|
|
50
|
+
"input": 0.55,
|
|
51
|
+
"output": 2.19
|
|
52
|
+
}
|
|
17
53
|
}
|
|
18
54
|
}
|