@diogonzafe/tokenwatch 0.1.17 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -4
- package/dist/adapters.d.cts +1 -1
- package/dist/adapters.d.ts +1 -1
- package/dist/cli.cjs +18 -4
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +18 -4
- package/dist/cli.js.map +1 -1
- package/dist/{index-Cy_sl3FI.d.cts → index-BQZaFcHQ.d.cts} +14 -0
- package/dist/{index-Cy_sl3FI.d.ts → index-BQZaFcHQ.d.ts} +14 -0
- package/dist/index.cjs +102 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -8
- package/dist/index.d.ts +23 -8
- package/dist/index.js +102 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1410,7 +1410,11 @@ ${issues}`);
|
|
|
1410
1410
|
}
|
|
1411
1411
|
function track(entry) {
|
|
1412
1412
|
const price = resolveModelPrice(entry.model);
|
|
1413
|
-
const costUSD = calculateCost(
|
|
1413
|
+
const costUSD = calculateCost(
|
|
1414
|
+
entry.inputTokens,
|
|
1415
|
+
entry.outputTokens + (entry.reasoningTokens ?? 0),
|
|
1416
|
+
price
|
|
1417
|
+
);
|
|
1414
1418
|
const full = {
|
|
1415
1419
|
...entry,
|
|
1416
1420
|
costUSD,
|
|
@@ -1446,6 +1450,7 @@ ${issues}`);
|
|
|
1446
1450
|
const byModel = {};
|
|
1447
1451
|
const bySession = {};
|
|
1448
1452
|
const byUser = {};
|
|
1453
|
+
const byFeature = {};
|
|
1449
1454
|
let totalInput = 0;
|
|
1450
1455
|
let totalOutput = 0;
|
|
1451
1456
|
let totalCost = 0;
|
|
@@ -1455,11 +1460,12 @@ ${issues}`);
|
|
|
1455
1460
|
totalOutput += e.outputTokens;
|
|
1456
1461
|
totalCost += e.costUSD;
|
|
1457
1462
|
if (e.timestamp > lastTimestamp) lastTimestamp = e.timestamp;
|
|
1458
|
-
const m = byModel[e.model] ??= { costUSD: 0, calls: 0, tokens: { input: 0, output: 0 } };
|
|
1463
|
+
const m = byModel[e.model] ??= { costUSD: 0, calls: 0, tokens: { input: 0, output: 0, reasoning: 0 } };
|
|
1459
1464
|
m.costUSD += e.costUSD;
|
|
1460
1465
|
m.calls += 1;
|
|
1461
1466
|
m.tokens.input += e.inputTokens;
|
|
1462
1467
|
m.tokens.output += e.outputTokens;
|
|
1468
|
+
m.tokens.reasoning += e.reasoningTokens ?? 0;
|
|
1463
1469
|
if (e.sessionId) {
|
|
1464
1470
|
const s = bySession[e.sessionId] ??= { costUSD: 0, calls: 0 };
|
|
1465
1471
|
s.costUSD += e.costUSD;
|
|
@@ -1470,6 +1476,11 @@ ${issues}`);
|
|
|
1470
1476
|
u.costUSD += e.costUSD;
|
|
1471
1477
|
u.calls += 1;
|
|
1472
1478
|
}
|
|
1479
|
+
if (e.feature) {
|
|
1480
|
+
const f = byFeature[e.feature] ??= { costUSD: 0, calls: 0 };
|
|
1481
|
+
f.costUSD += e.costUSD;
|
|
1482
|
+
f.calls += 1;
|
|
1483
|
+
}
|
|
1473
1484
|
}
|
|
1474
1485
|
return {
|
|
1475
1486
|
totalCostUSD: totalCost,
|
|
@@ -1477,6 +1488,7 @@ ${issues}`);
|
|
|
1477
1488
|
byModel,
|
|
1478
1489
|
bySession,
|
|
1479
1490
|
byUser,
|
|
1491
|
+
byFeature,
|
|
1480
1492
|
period: { from: startedAt, to: lastTimestamp }
|
|
1481
1493
|
};
|
|
1482
1494
|
}
|
|
@@ -1492,16 +1504,18 @@ ${issues}`);
|
|
|
1492
1504
|
}
|
|
1493
1505
|
async function exportCSV() {
|
|
1494
1506
|
const entries = await Promise.resolve(storage.getAll());
|
|
1495
|
-
const header = "timestamp,model,inputTokens,outputTokens,costUSD,sessionId,userId";
|
|
1507
|
+
const header = "timestamp,model,inputTokens,outputTokens,reasoningTokens,costUSD,sessionId,userId,feature";
|
|
1496
1508
|
const rows = entries.map(
|
|
1497
1509
|
(e) => [
|
|
1498
1510
|
csvEscape(e.timestamp),
|
|
1499
1511
|
csvEscape(e.model),
|
|
1500
1512
|
e.inputTokens,
|
|
1501
1513
|
e.outputTokens,
|
|
1514
|
+
e.reasoningTokens ?? 0,
|
|
1502
1515
|
e.costUSD.toFixed(8),
|
|
1503
1516
|
csvEscape(e.sessionId ?? ""),
|
|
1504
|
-
csvEscape(e.userId ?? "")
|
|
1517
|
+
csvEscape(e.userId ?? ""),
|
|
1518
|
+
csvEscape(e.feature ?? "")
|
|
1505
1519
|
].join(",")
|
|
1506
1520
|
);
|
|
1507
1521
|
return [header, ...rows].join("\n");
|
|
@@ -1527,42 +1541,46 @@ function csvEscape(value) {
|
|
|
1527
1541
|
|
|
1528
1542
|
// src/providers/openai.ts
|
|
1529
1543
|
function extractMeta(params) {
|
|
1530
|
-
const { __sessionId, __userId, ...cleaned } = params;
|
|
1544
|
+
const { __sessionId, __userId, __feature, ...cleaned } = params;
|
|
1531
1545
|
return {
|
|
1532
1546
|
cleaned,
|
|
1533
1547
|
sessionId: typeof __sessionId === "string" ? __sessionId : void 0,
|
|
1534
|
-
userId: typeof __userId === "string" ? __userId : void 0
|
|
1548
|
+
userId: typeof __userId === "string" ? __userId : void 0,
|
|
1549
|
+
feature: typeof __feature === "string" ? __feature : void 0
|
|
1535
1550
|
};
|
|
1536
1551
|
}
|
|
1537
1552
|
function extractUsage(usage) {
|
|
1538
|
-
if (!usage) return { inputTokens: 0, outputTokens: 0 };
|
|
1553
|
+
if (!usage) return { inputTokens: 0, outputTokens: 0, reasoningTokens: 0 };
|
|
1539
1554
|
return {
|
|
1540
1555
|
inputTokens: usage.prompt_tokens ?? usage.input_tokens ?? 0,
|
|
1541
|
-
outputTokens: usage.completion_tokens ?? usage.output_tokens ?? 0
|
|
1556
|
+
outputTokens: usage.completion_tokens ?? usage.output_tokens ?? 0,
|
|
1557
|
+
reasoningTokens: usage.completion_tokens_details?.reasoning_tokens ?? 0
|
|
1542
1558
|
};
|
|
1543
1559
|
}
|
|
1544
|
-
function trackWithMeta(tracker, model, inputTokens, outputTokens, sessionId, userId) {
|
|
1560
|
+
function trackWithMeta(tracker, model, inputTokens, outputTokens, reasoningTokens, sessionId, userId, feature) {
|
|
1545
1561
|
tracker.track({
|
|
1546
1562
|
model,
|
|
1547
1563
|
inputTokens,
|
|
1548
1564
|
outputTokens,
|
|
1565
|
+
...reasoningTokens > 0 && { reasoningTokens },
|
|
1549
1566
|
...sessionId !== void 0 && { sessionId },
|
|
1550
|
-
...userId !== void 0 && { userId }
|
|
1567
|
+
...userId !== void 0 && { userId },
|
|
1568
|
+
...feature !== void 0 && { feature }
|
|
1551
1569
|
});
|
|
1552
1570
|
}
|
|
1553
|
-
async function* wrapStream(stream, model, sessionId, userId, tracker) {
|
|
1571
|
+
async function* wrapStream(stream, model, sessionId, userId, feature, tracker) {
|
|
1554
1572
|
let lastChunk;
|
|
1555
1573
|
for await (const chunk of stream) {
|
|
1556
1574
|
lastChunk = chunk;
|
|
1557
1575
|
yield chunk;
|
|
1558
1576
|
}
|
|
1559
|
-
const { inputTokens, outputTokens } = extractUsage(lastChunk?.usage);
|
|
1577
|
+
const { inputTokens, outputTokens, reasoningTokens } = extractUsage(lastChunk?.usage);
|
|
1560
1578
|
if (!lastChunk?.usage) {
|
|
1561
1579
|
console.warn(
|
|
1562
1580
|
`[tokenwatch] No usage data in stream for model "${model}". Cost recorded as $0. Pass stream_options: { include_usage: true } to get accurate costs.`
|
|
1563
1581
|
);
|
|
1564
1582
|
}
|
|
1565
|
-
trackWithMeta(tracker, model, inputTokens, outputTokens, sessionId, userId);
|
|
1583
|
+
trackWithMeta(tracker, model, inputTokens, outputTokens, reasoningTokens, sessionId, userId, feature);
|
|
1566
1584
|
}
|
|
1567
1585
|
function wrapOpenAI(client, tracker) {
|
|
1568
1586
|
const proxiedCompletions = new Proxy(client.chat.completions, {
|
|
@@ -1570,7 +1588,7 @@ function wrapOpenAI(client, tracker) {
|
|
|
1570
1588
|
if (prop !== "create")
|
|
1571
1589
|
return target[prop];
|
|
1572
1590
|
return async function(params) {
|
|
1573
|
-
const { cleaned, sessionId, userId } = extractMeta(params);
|
|
1591
|
+
const { cleaned, sessionId, userId, feature } = extractMeta(params);
|
|
1574
1592
|
const model = typeof cleaned["model"] === "string" ? cleaned["model"] : "unknown";
|
|
1575
1593
|
const result = await target.create(cleaned);
|
|
1576
1594
|
if (result && typeof result === "object" && Symbol.asyncIterator in result) {
|
|
@@ -1579,18 +1597,21 @@ function wrapOpenAI(client, tracker) {
|
|
|
1579
1597
|
model,
|
|
1580
1598
|
sessionId,
|
|
1581
1599
|
userId,
|
|
1600
|
+
feature,
|
|
1582
1601
|
tracker
|
|
1583
1602
|
);
|
|
1584
1603
|
}
|
|
1585
1604
|
const completion = result;
|
|
1586
|
-
const { inputTokens, outputTokens } = extractUsage(completion.usage);
|
|
1605
|
+
const { inputTokens, outputTokens, reasoningTokens } = extractUsage(completion.usage);
|
|
1587
1606
|
trackWithMeta(
|
|
1588
1607
|
tracker,
|
|
1589
1608
|
completion.model ?? model,
|
|
1590
1609
|
inputTokens,
|
|
1591
1610
|
outputTokens,
|
|
1611
|
+
reasoningTokens,
|
|
1592
1612
|
sessionId,
|
|
1593
|
-
userId
|
|
1613
|
+
userId,
|
|
1614
|
+
feature
|
|
1594
1615
|
);
|
|
1595
1616
|
return result;
|
|
1596
1617
|
};
|
|
@@ -1602,9 +1623,25 @@ function wrapOpenAI(client, tracker) {
|
|
|
1602
1623
|
return target[prop];
|
|
1603
1624
|
}
|
|
1604
1625
|
});
|
|
1626
|
+
const proxiedEmbeddings = client.embeddings ? new Proxy(client.embeddings, {
|
|
1627
|
+
get(target, prop) {
|
|
1628
|
+
if (prop !== "create")
|
|
1629
|
+
return target[prop];
|
|
1630
|
+
return async function(params) {
|
|
1631
|
+
const { cleaned, sessionId, userId, feature } = extractMeta(params);
|
|
1632
|
+
const model = typeof cleaned["model"] === "string" ? cleaned["model"] : "unknown";
|
|
1633
|
+
const result = await target.create(cleaned);
|
|
1634
|
+
const embedding = result;
|
|
1635
|
+
const inputTokens = embedding.usage?.total_tokens ?? 0;
|
|
1636
|
+
trackWithMeta(tracker, embedding.model ?? model, inputTokens, 0, 0, sessionId, userId, feature);
|
|
1637
|
+
return result;
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
}) : void 0;
|
|
1605
1641
|
return new Proxy(client, {
|
|
1606
1642
|
get(target, prop) {
|
|
1607
1643
|
if (prop === "chat") return proxiedChat;
|
|
1644
|
+
if (prop === "embeddings") return proxiedEmbeddings;
|
|
1608
1645
|
return target[prop];
|
|
1609
1646
|
}
|
|
1610
1647
|
});
|
|
@@ -1612,11 +1649,12 @@ function wrapOpenAI(client, tracker) {
|
|
|
1612
1649
|
|
|
1613
1650
|
// src/providers/anthropic.ts
|
|
1614
1651
|
function extractMeta2(params) {
|
|
1615
|
-
const { __sessionId, __userId, ...cleaned } = params;
|
|
1652
|
+
const { __sessionId, __userId, __feature, ...cleaned } = params;
|
|
1616
1653
|
return {
|
|
1617
1654
|
cleaned,
|
|
1618
1655
|
sessionId: typeof __sessionId === "string" ? __sessionId : void 0,
|
|
1619
|
-
userId: typeof __userId === "string" ? __userId : void 0
|
|
1656
|
+
userId: typeof __userId === "string" ? __userId : void 0,
|
|
1657
|
+
feature: typeof __feature === "string" ? __feature : void 0
|
|
1620
1658
|
};
|
|
1621
1659
|
}
|
|
1622
1660
|
function extractUsage2(usage) {
|
|
@@ -1626,18 +1664,31 @@ function extractUsage2(usage) {
|
|
|
1626
1664
|
outputTokens: usage.output_tokens ?? 0
|
|
1627
1665
|
};
|
|
1628
1666
|
}
|
|
1629
|
-
function
|
|
1667
|
+
function extractThinkingTokenApprox(content) {
|
|
1668
|
+
if (!content) return 0;
|
|
1669
|
+
const chars = content.filter((b) => b.type === "thinking").reduce((sum, b) => sum + (b.thinking?.length ?? 0), 0);
|
|
1670
|
+
return chars > 0 ? Math.round(chars / 4) : 0;
|
|
1671
|
+
}
|
|
1672
|
+
function trackWithMeta2(tracker, model, inputTokens, outputTokens, reasoningTokens, sessionId, userId, feature) {
|
|
1630
1673
|
tracker.track({
|
|
1631
1674
|
model,
|
|
1632
1675
|
inputTokens,
|
|
1633
1676
|
outputTokens,
|
|
1677
|
+
// For Anthropic, reasoningTokens is informational (thinking already in outputTokens).
|
|
1678
|
+
// Pass 0 so tracker does not add it to cost (tracker only adds when > 0 AND separate).
|
|
1679
|
+
// We store it as a field but the tracker cost formula adds reasoningTokens to outputTokens,
|
|
1680
|
+
// so we must NOT pass it here to avoid double-counting.
|
|
1634
1681
|
...sessionId !== void 0 && { sessionId },
|
|
1635
|
-
...userId !== void 0 && { userId }
|
|
1682
|
+
...userId !== void 0 && { userId },
|
|
1683
|
+
...feature !== void 0 && { feature },
|
|
1684
|
+
...reasoningTokens > 0 && { reasoningTokens }
|
|
1636
1685
|
});
|
|
1637
1686
|
}
|
|
1638
|
-
async function* wrapStream2(stream, model, sessionId, userId, tracker) {
|
|
1687
|
+
async function* wrapStream2(stream, model, sessionId, userId, feature, tracker) {
|
|
1639
1688
|
let inputTokens = 0;
|
|
1640
1689
|
let outputTokens = 0;
|
|
1690
|
+
let currentBlockIsThinking = false;
|
|
1691
|
+
let thinkingCharCount = 0;
|
|
1641
1692
|
for await (const event of stream) {
|
|
1642
1693
|
yield event;
|
|
1643
1694
|
if (event.type === "message_start" && event.message?.usage) {
|
|
@@ -1646,8 +1697,18 @@ async function* wrapStream2(stream, model, sessionId, userId, tracker) {
|
|
|
1646
1697
|
if (event.type === "message_delta" && event.usage) {
|
|
1647
1698
|
outputTokens = event.usage.output_tokens ?? 0;
|
|
1648
1699
|
}
|
|
1700
|
+
if (event.type === "content_block_start") {
|
|
1701
|
+
currentBlockIsThinking = event.content_block?.type === "thinking";
|
|
1702
|
+
}
|
|
1703
|
+
if (event.type === "content_block_stop") {
|
|
1704
|
+
currentBlockIsThinking = false;
|
|
1705
|
+
}
|
|
1706
|
+
if (event.type === "content_block_delta" && currentBlockIsThinking && event.delta?.thinking) {
|
|
1707
|
+
thinkingCharCount += event.delta.thinking.length;
|
|
1708
|
+
}
|
|
1649
1709
|
}
|
|
1650
|
-
|
|
1710
|
+
const reasoningTokens = thinkingCharCount > 0 ? Math.round(thinkingCharCount / 4) : 0;
|
|
1711
|
+
trackWithMeta2(tracker, model, inputTokens, outputTokens, reasoningTokens, sessionId, userId, feature);
|
|
1651
1712
|
}
|
|
1652
1713
|
function wrapAnthropic(client, tracker) {
|
|
1653
1714
|
const proxiedMessages = new Proxy(client.messages, {
|
|
@@ -1655,7 +1716,7 @@ function wrapAnthropic(client, tracker) {
|
|
|
1655
1716
|
if (prop !== "create")
|
|
1656
1717
|
return target[prop];
|
|
1657
1718
|
return async function(params) {
|
|
1658
|
-
const { cleaned, sessionId, userId } = extractMeta2(params);
|
|
1719
|
+
const { cleaned, sessionId, userId, feature } = extractMeta2(params);
|
|
1659
1720
|
const model = typeof cleaned["model"] === "string" ? cleaned["model"] : "unknown";
|
|
1660
1721
|
const result = await target.create(cleaned);
|
|
1661
1722
|
if (result && typeof result === "object" && Symbol.asyncIterator in result) {
|
|
@@ -1664,18 +1725,22 @@ function wrapAnthropic(client, tracker) {
|
|
|
1664
1725
|
model,
|
|
1665
1726
|
sessionId,
|
|
1666
1727
|
userId,
|
|
1728
|
+
feature,
|
|
1667
1729
|
tracker
|
|
1668
1730
|
);
|
|
1669
1731
|
}
|
|
1670
1732
|
const message = result;
|
|
1671
1733
|
const { inputTokens, outputTokens } = extractUsage2(message.usage);
|
|
1734
|
+
const reasoningTokens = extractThinkingTokenApprox(message.content);
|
|
1672
1735
|
trackWithMeta2(
|
|
1673
1736
|
tracker,
|
|
1674
1737
|
message.model ?? model,
|
|
1675
1738
|
inputTokens,
|
|
1676
1739
|
outputTokens,
|
|
1740
|
+
reasoningTokens,
|
|
1677
1741
|
sessionId,
|
|
1678
|
-
userId
|
|
1742
|
+
userId,
|
|
1743
|
+
feature
|
|
1679
1744
|
);
|
|
1680
1745
|
return result;
|
|
1681
1746
|
};
|
|
@@ -1696,7 +1761,11 @@ function wrapGemini(client, tracker) {
|
|
|
1696
1761
|
if (prop !== "getGenerativeModel")
|
|
1697
1762
|
return target[prop];
|
|
1698
1763
|
return function(modelParams) {
|
|
1699
|
-
const
|
|
1764
|
+
const { __sessionId, __userId, __feature, ...cleanedParams } = modelParams;
|
|
1765
|
+
const feature = typeof __feature === "string" ? __feature : void 0;
|
|
1766
|
+
const sessionId = typeof __sessionId === "string" ? __sessionId : void 0;
|
|
1767
|
+
const userId = typeof __userId === "string" ? __userId : void 0;
|
|
1768
|
+
const modelInstance = target.getGenerativeModel(cleanedParams);
|
|
1700
1769
|
const modelId = modelParams.model;
|
|
1701
1770
|
return new Proxy(modelInstance, {
|
|
1702
1771
|
get(mTarget, mProp) {
|
|
@@ -1707,7 +1776,10 @@ function wrapGemini(client, tracker) {
|
|
|
1707
1776
|
tracker.track({
|
|
1708
1777
|
model: modelId,
|
|
1709
1778
|
inputTokens: meta?.promptTokenCount ?? 0,
|
|
1710
|
-
outputTokens: meta?.candidatesTokenCount ?? 0
|
|
1779
|
+
outputTokens: meta?.candidatesTokenCount ?? 0,
|
|
1780
|
+
...sessionId !== void 0 && { sessionId },
|
|
1781
|
+
...userId !== void 0 && { userId },
|
|
1782
|
+
...feature !== void 0 && { feature }
|
|
1711
1783
|
});
|
|
1712
1784
|
return result;
|
|
1713
1785
|
};
|
|
@@ -1720,7 +1792,10 @@ function wrapGemini(client, tracker) {
|
|
|
1720
1792
|
tracker.track({
|
|
1721
1793
|
model: modelId,
|
|
1722
1794
|
inputTokens: meta?.promptTokenCount ?? 0,
|
|
1723
|
-
outputTokens: meta?.candidatesTokenCount ?? 0
|
|
1795
|
+
outputTokens: meta?.candidatesTokenCount ?? 0,
|
|
1796
|
+
...sessionId !== void 0 && { sessionId },
|
|
1797
|
+
...userId !== void 0 && { userId },
|
|
1798
|
+
...feature !== void 0 && { feature }
|
|
1724
1799
|
});
|
|
1725
1800
|
}).catch(() => {
|
|
1726
1801
|
});
|