Skip to content

Commit fb5cb3b

Browse files
committed
feat: add tokentally
1 parent f63bd90 commit fb5cb3b

23 files changed

Lines changed: 2080 additions & 0 deletions

.biomeignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
coverage/**
2+
dist/**
3+
node_modules/**

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.DS_Store
2+
node_modules
3+
dist
4+
coverage
5+
.turbo
6+
.tsbuildinfo
7+

README.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# tokentally
2+
3+
Token usage in, dollar totals out.
4+
5+
Small TypeScript library for:
6+
- Normalizing token usage across providers
7+
- Resolving per-token pricing (static maps, LiteLLM catalog, OpenRouter catalog)
8+
- Estimating and aggregating USD cost
9+
10+
## Install
11+
12+
```bash
13+
pnpm add tokentally
14+
```
15+
16+
## Core usage (browser-safe)
17+
18+
```ts
19+
import { estimateUsdCost, normalizeTokenUsage, pricingFromUsdPerMillion } from 'tokentally';
20+
21+
const usage = normalizeTokenUsage({ prompt_tokens: 1000, completion_tokens: 250 });
22+
const pricing = pricingFromUsdPerMillion({ inputUsdPerMillion: 1.75, outputUsdPerMillion: 14 });
23+
24+
const cost = estimateUsdCost({ usage, pricing });
25+
// { inputUsd: ..., outputUsd: ..., totalUsd: ... }
26+
```
27+
28+
## Node helpers (catalog sources)
29+
30+
### LiteLLM pricing + limits
31+
32+
```ts
33+
import { loadLiteLlmCatalog, resolveLiteLlmPricing, resolveLiteLlmMaxOutputTokens } from 'tokentally/node';
34+
35+
const { catalog } = await loadLiteLlmCatalog({ env: process.env, fetchImpl: fetch });
36+
const pricing = catalog ? resolveLiteLlmPricing(catalog, 'openai/gpt-5.2') : null;
37+
const maxOut = catalog ? resolveLiteLlmMaxOutputTokens(catalog, 'openai/gpt-5.2') : null;
38+
```
39+
40+
### OpenRouter pricing (optional)
41+
42+
```ts
43+
import { fetchOpenRouterPricingMap, resolvePricingFromMap } from 'tokentally/node';
44+
45+
const map = await fetchOpenRouterPricingMap({ apiKey: process.env.OPENROUTER_API_KEY!, fetchImpl: fetch });
46+
const pricing = resolvePricingFromMap(map, 'openai/gpt-5.2');
47+
```
48+
49+
## API
50+
51+
- `normalizeTokenUsage(raw)``{ inputTokens, outputTokens, reasoningTokens, totalTokens } | null`
52+
- `pricingFromUsdPerMillion({ inputUsdPerMillion, outputUsdPerMillion })`
53+
- `estimateUsdCost({ usage, pricing })`
54+
- `tallyCosts(calls)` → totals + per-model breakdown
55+
56+
## Non-goals
57+
58+
- Perfect accounting. This is a **best-effort estimate** based on the pricing source you provide.
59+
- Provider-specific invoice reconciliation.
60+
61+
## License
62+
63+
MIT
64+

biome.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"$schema": "https://biomejs.dev/schemas/2.3.10/schema.json",
3+
"formatter": {
4+
"indentWidth": 2,
5+
"lineWidth": 100
6+
},
7+
"linter": {
8+
"enabled": true,
9+
"rules": {
10+
"recommended": true,
11+
"correctness": {
12+
"noUnusedImports": "error",
13+
"noUnusedVariables": {
14+
"level": "error",
15+
"options": {
16+
"ignoreRestSiblings": false
17+
}
18+
}
19+
}
20+
}
21+
}
22+
}

package.json

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"name": "tokentally",
3+
"version": "0.0.1",
4+
"description": "Token usage in, dollar totals out.",
5+
"type": "module",
6+
"license": "MIT",
7+
"repository": {
8+
"type": "git",
9+
"url": "git+https://github.com/steipete/tokentally.git"
10+
},
11+
"exports": {
12+
".": {
13+
"types": "./dist/index.d.ts",
14+
"import": "./dist/index.js"
15+
},
16+
"./node": {
17+
"types": "./dist/node/index.d.ts",
18+
"import": "./dist/node/index.js"
19+
}
20+
},
21+
"files": [
22+
"dist",
23+
"README.md",
24+
"LICENSE"
25+
],
26+
"engines": {
27+
"node": ">=20"
28+
},
29+
"packageManager": "pnpm@10.25.0",
30+
"scripts": {
31+
"build": "tsc -p tsconfig.build.json",
32+
"prepare": "pnpm build",
33+
"typecheck": "tsc -p tsconfig.json --noEmit",
34+
"format": "biome format --write src tests package.json tsconfig.json tsconfig.build.json vitest.config.ts README.md",
35+
"lint": "biome check src tests package.json tsconfig.json tsconfig.build.json vitest.config.ts README.md",
36+
"lint:fix": "biome check --write src tests package.json tsconfig.json tsconfig.build.json vitest.config.ts README.md",
37+
"test": "vitest run",
38+
"test:coverage": "vitest run --coverage",
39+
"check": "pnpm lint && pnpm typecheck && pnpm test:coverage"
40+
},
41+
"devDependencies": {
42+
"@biomejs/biome": "^2.3.10",
43+
"@types/node": "^25.0.3",
44+
"@vitest/coverage-v8": "^4.0.16",
45+
"typescript": "^5.9.3",
46+
"vitest": "^4.0.16"
47+
}
48+
}

0 commit comments

Comments
 (0)
Sponsor
SponsoredKunjungi sekarang
Promo