@sisu-ai/mw-usage-tracker 1.0.1 → 3.0.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 +21 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +64 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,8 +16,19 @@ import { usageTracker } from '@sisu-ai/mw-usage-tracker';
|
|
|
16
16
|
|
|
17
17
|
const app = new Agent()
|
|
18
18
|
.use(usageTracker({
|
|
19
|
-
'openai:gpt-4o-mini': {
|
|
20
|
-
|
|
19
|
+
'openai:gpt-4o-mini': {
|
|
20
|
+
// Preferred: prices per 1M tokens (matches provider docs)
|
|
21
|
+
inputPer1M: 0.15,
|
|
22
|
+
outputPer1M: 0.60,
|
|
23
|
+
// Optional vision pricing (choose one):
|
|
24
|
+
// a) Per 1K images (e.g., $0.217/K images)
|
|
25
|
+
imagePer1K: 0.217,
|
|
26
|
+
// b) Approximate per-1K "image tokens"
|
|
27
|
+
// imageInputPer1K: 0.217,
|
|
28
|
+
// imageTokenPerImage: 1000,
|
|
29
|
+
},
|
|
30
|
+
// Fallback default for other models
|
|
31
|
+
'*': { inputPer1M: 0.15, outputPer1M: 0.60 },
|
|
21
32
|
}, { logPerCall: true }))
|
|
22
33
|
```
|
|
23
34
|
|
|
@@ -25,8 +36,16 @@ const app = new Agent()
|
|
|
25
36
|
- Wraps `ctx.model.generate` for the duration of the pipeline.
|
|
26
37
|
- Accumulates `promptTokens`, `completionTokens`, `totalTokens` from `ModelResponse.usage`.
|
|
27
38
|
- If a price table is provided, computes `costUSD` per call and totals.
|
|
39
|
+
- Optional: for vision models
|
|
40
|
+
- Providers vary: some include image cost in native token usage; others bill separately per image.
|
|
41
|
+
- If billed per image batch, set `imagePer1K` (e.g., 0.217 USD per 1K images). The tracker converts this to per-image.
|
|
42
|
+
- Or configure `imageInputPer1K` + `imageTokenPerImage` to approximate per-1K image-token pricing. In that mode we split
|
|
43
|
+
prompt tokens into estimated `imageTokens = imageCount * imageTokenPerImage` (default 1000) and
|
|
44
|
+
`textPromptTokens = promptTokens - imageTokens`, then price them separately.
|
|
28
45
|
- Writes totals to `ctx.state.usage` and logs them at the end.
|
|
46
|
+
- Cost is rounded to 6 decimals to avoid showing small calls as 0.00.
|
|
29
47
|
|
|
30
48
|
## Notes
|
|
31
49
|
- Each adapter should map its native usage fields to `ModelResponse.usage`.
|
|
32
50
|
- If a provider doesn’t return usage, you’ll get counts of calls only (no cost).
|
|
51
|
+
- Image cost estimation is an approximation unless your adapter returns precise image token usage.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import type { Middleware } from '@sisu-ai/core';
|
|
2
2
|
export type PriceTable = Record<string, {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
inputPer1M?: number;
|
|
4
|
+
outputPer1M?: number;
|
|
5
|
+
inputPer1K?: number;
|
|
6
|
+
outputPer1K?: number;
|
|
7
|
+
imagePerImage?: number;
|
|
8
|
+
imagePer1K?: number;
|
|
9
|
+
imageInputPer1K?: number;
|
|
10
|
+
imageTokenPerImage?: number;
|
|
5
11
|
}>;
|
|
6
12
|
export interface UsageTotals {
|
|
7
13
|
promptTokens: number;
|
|
8
14
|
completionTokens: number;
|
|
9
15
|
totalTokens: number;
|
|
10
16
|
costUSD?: number;
|
|
17
|
+
imageTokens?: number;
|
|
18
|
+
imageCount?: number;
|
|
11
19
|
}
|
|
12
20
|
export declare function usageTracker(prices: PriceTable, opts?: {
|
|
13
21
|
logPerCall?: boolean;
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,10 @@ export function usageTracker(prices, opts = {}) {
|
|
|
10
10
|
costUSD: Number(ctx.state?.usage?.costUSD ?? 0),
|
|
11
11
|
};
|
|
12
12
|
async function withUsage(...args) {
|
|
13
|
+
// Inspect request messages to estimate image inputs
|
|
14
|
+
const reqMessages = args?.[0];
|
|
15
|
+
const imageCount = countImageInputs(reqMessages);
|
|
16
|
+
const imageTokens = imageCount * Number(price?.imageTokenPerImage ?? 1000);
|
|
13
17
|
const out = await orig(...args);
|
|
14
18
|
const u = out.usage;
|
|
15
19
|
if (u) {
|
|
@@ -20,11 +24,41 @@ export function usageTracker(prices, opts = {}) {
|
|
|
20
24
|
totals.completionTokens += c;
|
|
21
25
|
totals.totalTokens += t;
|
|
22
26
|
if (price) {
|
|
23
|
-
|
|
27
|
+
// Resolve effective per-1K pricing from provided per-1M or per-1K fields
|
|
28
|
+
const inPer1K = price.inputPer1K ?? (price.inputPer1M != null ? price.inputPer1M / 1000 : undefined) ?? 0;
|
|
29
|
+
const outPer1K = price.outputPer1K ?? (price.outputPer1M != null ? price.outputPer1M / 1000 : undefined) ?? 0;
|
|
30
|
+
// Base text+completion token cost
|
|
31
|
+
const textPromptTokens = Math.max(0, p - (price.imageInputPer1K ? imageTokens : 0));
|
|
32
|
+
const tokenCost = (textPromptTokens / 1000) * inPer1K + (c / 1000) * outPer1K;
|
|
33
|
+
// Image cost (prefer per-image if provided)
|
|
34
|
+
const perImage = price.imagePerImage != null ? price.imagePerImage : (price.imagePer1K != null ? price.imagePer1K / 1000 : undefined);
|
|
35
|
+
const imageCost = perImage != null
|
|
36
|
+
? imageCount * perImage
|
|
37
|
+
: price.imageInputPer1K
|
|
38
|
+
? (imageTokens / 1000) * price.imageInputPer1K
|
|
39
|
+
: 0;
|
|
40
|
+
const cost = tokenCost + imageCost;
|
|
24
41
|
totals.costUSD = Number((totals.costUSD ?? 0) + cost);
|
|
42
|
+
if (imageCount > 0 && price.imageInputPer1K) {
|
|
43
|
+
totals.imageTokens = Number((totals.imageTokens ?? 0) + imageTokens);
|
|
44
|
+
totals.imageCount = Number((totals.imageCount ?? 0) + imageCount);
|
|
45
|
+
}
|
|
25
46
|
}
|
|
26
47
|
if (opts.logPerCall)
|
|
27
|
-
ctx.log.info?.('[usage] call', {
|
|
48
|
+
ctx.log.info?.('[usage] call', {
|
|
49
|
+
promptTokens: p, completionTokens: c, totalTokens: t,
|
|
50
|
+
imageTokens: (imageCount > 0 && price?.imageInputPer1K) ? imageTokens : undefined,
|
|
51
|
+
imageCount: imageCount > 0 ? imageCount : undefined,
|
|
52
|
+
estCostUSD: price ? (() => {
|
|
53
|
+
const inPer1K = price.inputPer1K ?? (price.inputPer1M != null ? price.inputPer1M / 1000 : 0);
|
|
54
|
+
const outPer1K = price.outputPer1K ?? (price.outputPer1M != null ? price.outputPer1M / 1000 : 0);
|
|
55
|
+
const textPromptTokens = Math.max(0, p - (price?.imageInputPer1K ? imageTokens : 0));
|
|
56
|
+
const perImage = price?.imagePerImage != null ? price.imagePerImage : (price?.imagePer1K != null ? price.imagePer1K / 1000 : undefined);
|
|
57
|
+
const tokenCost = (textPromptTokens / 1000) * inPer1K + (c / 1000) * outPer1K;
|
|
58
|
+
const imageCost = perImage != null ? (imageCount * perImage) : (price?.imageInputPer1K ? (imageTokens / 1000) * price.imageInputPer1K : 0);
|
|
59
|
+
return roundUSD(tokenCost + imageCost);
|
|
60
|
+
})() : undefined,
|
|
61
|
+
});
|
|
28
62
|
}
|
|
29
63
|
return out;
|
|
30
64
|
}
|
|
@@ -39,8 +73,34 @@ export function usageTracker(prices, opts = {}) {
|
|
|
39
73
|
ctx.state.usage.completionTokens = totals.completionTokens;
|
|
40
74
|
ctx.state.usage.totalTokens = totals.totalTokens;
|
|
41
75
|
if (price)
|
|
42
|
-
ctx.state.usage.costUSD =
|
|
76
|
+
ctx.state.usage.costUSD = roundUSD(totals.costUSD ?? 0);
|
|
77
|
+
if (totals.imageTokens)
|
|
78
|
+
ctx.state.usage.imageTokens = totals.imageTokens;
|
|
79
|
+
if (totals.imageCount)
|
|
80
|
+
ctx.state.usage.imageCount = totals.imageCount;
|
|
43
81
|
ctx.log.info?.('[usage] totals', ctx.state.usage);
|
|
44
82
|
};
|
|
45
83
|
}
|
|
46
|
-
function
|
|
84
|
+
function roundUSD(n) { return Math.round(n * 1e6) / 1e6; }
|
|
85
|
+
function countImageInputs(msgs) {
|
|
86
|
+
if (!Array.isArray(msgs))
|
|
87
|
+
return 0;
|
|
88
|
+
let count = 0;
|
|
89
|
+
for (const m of msgs) {
|
|
90
|
+
const c = m?.content;
|
|
91
|
+
if (Array.isArray(c)) {
|
|
92
|
+
for (const part of c) {
|
|
93
|
+
if (part && typeof part === 'object' && (part.type === 'image_url' || part.type === 'image'))
|
|
94
|
+
count += 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Convenience shapes supported by adapters (count regardless of content presence)
|
|
98
|
+
if (Array.isArray(m?.images))
|
|
99
|
+
count += m.images.length;
|
|
100
|
+
if (typeof m?.image_url === 'string')
|
|
101
|
+
count += 1;
|
|
102
|
+
if (typeof m?.image === 'string')
|
|
103
|
+
count += 1;
|
|
104
|
+
}
|
|
105
|
+
return count;
|
|
106
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sisu-ai/mw-usage-tracker",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"build": "tsc -b"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
|
-
"@sisu-ai/core": "0.
|
|
17
|
+
"@sisu-ai/core": "1.0.0"
|
|
18
18
|
},
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|