ai-token-estimator 1.0.2 → 1.1.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 +110 -5
- package/dist/index.cjs +347 -18
- package/dist/index.d.cts +64 -4
- package/dist/index.d.ts +64 -4
- package/dist/index.js +343 -18
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -39,6 +39,29 @@ console.log(getAvailableModels());
|
|
|
39
39
|
// ['gpt-5.2', 'gpt-4o', 'claude-opus-4.5', 'gemini-3-pro', ...]
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
## Exact OpenAI tokenization (BPE)
|
|
43
|
+
|
|
44
|
+
This package includes **exact tokenization for OpenAI models** using a tiktoken-compatible BPE tokenizer (via `gpt-tokenizer`).
|
|
45
|
+
|
|
46
|
+
Notes:
|
|
47
|
+
- Encodings are **lazy-loaded on first use** (one-time cost per encoding).
|
|
48
|
+
- Exact tokenization is **slower** than heuristic estimation; `estimate()` defaults to `'heuristic'` to keep existing behavior fast.
|
|
49
|
+
- `encode` / `decode` and `estimate({ tokenizer: 'openai_exact' })` require **Node.js** (uses `node:module` under the hood).
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { encode, decode } from 'ai-token-estimator';
|
|
53
|
+
|
|
54
|
+
const text = 'Hello, world!';
|
|
55
|
+
const tokens = encode(text, { model: 'gpt-5.1' }); // exact OpenAI token IDs
|
|
56
|
+
const roundTrip = decode(tokens, { model: 'gpt-5.1' });
|
|
57
|
+
|
|
58
|
+
console.log(tokens.length);
|
|
59
|
+
console.log(roundTrip); // "Hello, world!"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Supported encodings:
|
|
63
|
+
`r50k_base`, `p50k_base`, `p50k_edit`, `cl100k_base`, `o200k_base`, `o200k_harmony`
|
|
64
|
+
|
|
42
65
|
## API Reference
|
|
43
66
|
|
|
44
67
|
### `estimate(input: EstimateInput): EstimateOutput`
|
|
@@ -52,6 +75,7 @@ interface EstimateInput {
|
|
|
52
75
|
text: string; // The text to estimate tokens for
|
|
53
76
|
model: string; // Model ID (e.g., 'gpt-4o', 'claude-opus-4.5')
|
|
54
77
|
rounding?: 'ceil' | 'round' | 'floor'; // Rounding strategy (default: 'ceil')
|
|
78
|
+
tokenizer?: 'heuristic' | 'openai_exact' | 'auto'; // Token counting strategy (default: 'heuristic')
|
|
55
79
|
}
|
|
56
80
|
```
|
|
57
81
|
|
|
@@ -64,13 +88,36 @@ interface EstimateOutput {
|
|
|
64
88
|
estimatedTokens: number; // Estimated token count (integer)
|
|
65
89
|
estimatedInputCost: number; // Estimated cost in USD
|
|
66
90
|
charsPerToken: number; // The ratio used for this model
|
|
91
|
+
tokenizerMode?: 'heuristic' | 'openai_exact' | 'auto'; // Which strategy was used
|
|
92
|
+
encodingUsed?: string; // OpenAI encoding when using exact tokenization
|
|
67
93
|
}
|
|
68
94
|
```
|
|
69
95
|
|
|
96
|
+
### `countTokens(input: TokenCountInput): TokenCountOutput`
|
|
97
|
+
|
|
98
|
+
Counts tokens for a given model:
|
|
99
|
+
- OpenAI models: **exact** BPE tokenization
|
|
100
|
+
- Other providers: heuristic estimate
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { countTokens } from 'ai-token-estimator';
|
|
104
|
+
|
|
105
|
+
const result = countTokens({ text: 'Hello, world!', model: 'gpt-5.1' });
|
|
106
|
+
// { tokens: 4, exact: true, encoding: 'o200k_base' }
|
|
107
|
+
```
|
|
108
|
+
|
|
70
109
|
### `getAvailableModels(): string[]`
|
|
71
110
|
|
|
72
111
|
Returns an array of all supported model IDs.
|
|
73
112
|
|
|
113
|
+
### `encode(text: string, options?: EncodeOptions): number[]`
|
|
114
|
+
|
|
115
|
+
Encodes text into **OpenAI token IDs** using tiktoken-compatible BPE tokenization.
|
|
116
|
+
|
|
117
|
+
### `decode(tokens: Iterable<number>, options?: { encoding?: OpenAIEncoding; model?: string }): string`
|
|
118
|
+
|
|
119
|
+
Decodes OpenAI token IDs back into text using the selected encoding/model.
|
|
120
|
+
|
|
74
121
|
### `getModelConfig(model: string): ModelConfig`
|
|
75
122
|
|
|
76
123
|
Returns the configuration for a specific model. Throws if the model is not found.
|
|
@@ -108,6 +155,14 @@ This package counts Unicode code points, not UTF-16 code units. This means:
|
|
|
108
155
|
- Accented characters count correctly
|
|
109
156
|
- Most source code characters count as 1
|
|
110
157
|
|
|
158
|
+
## Benchmarks (repo only)
|
|
159
|
+
|
|
160
|
+
This repository includes a small benchmark script to compare heuristic vs exact OpenAI tokenization:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
npm run benchmark:tokenizer
|
|
164
|
+
```
|
|
165
|
+
|
|
111
166
|
<!-- SUPPORTED_MODELS_START -->
|
|
112
167
|
## Supported Models
|
|
113
168
|
|
|
@@ -117,20 +172,70 @@ This package counts Unicode code points, not UTF-16 code units. This means:
|
|
|
117
172
|
|
|
118
173
|
| Model | Chars/Token | Input Cost (per 1M tokens) |
|
|
119
174
|
|-------|-------------|---------------------------|
|
|
120
|
-
|
|
|
175
|
+
| babbage-002 | 4 | $0.40 |
|
|
176
|
+
| chatgpt-4o-latest | 4 | $5.00 |
|
|
177
|
+
| chatgpt-image-latest | 4 | $5.00 |
|
|
178
|
+
| codex-mini-latest | 4 | $1.50 |
|
|
179
|
+
| computer-use-preview | 4 | $3.00 |
|
|
180
|
+
| davinci-002 | 4 | $2.00 |
|
|
181
|
+
| gpt-3.5-0301 | 4 | $1.50 |
|
|
182
|
+
| gpt-3.5-turbo | 4 | $0.50 |
|
|
183
|
+
| gpt-3.5-turbo-0125 | 4 | $0.50 |
|
|
184
|
+
| gpt-3.5-turbo-0613 | 4 | $1.50 |
|
|
185
|
+
| gpt-3.5-turbo-1106 | 4 | $1.00 |
|
|
186
|
+
| gpt-3.5-turbo-16k-0613 | 4 | $3.00 |
|
|
187
|
+
| gpt-3.5-turbo-instruct | 4 | $1.50 |
|
|
188
|
+
| gpt-4-0125-preview | 4 | $10.00 |
|
|
189
|
+
| gpt-4-0314 | 4 | $30.00 |
|
|
190
|
+
| gpt-4-0613 | 4 | $30.00 |
|
|
191
|
+
| gpt-4-1106-preview | 4 | $10.00 |
|
|
192
|
+
| gpt-4-1106-vision-preview | 4 | $10.00 |
|
|
193
|
+
| gpt-4-32k | 4 | $60.00 |
|
|
194
|
+
| gpt-4-turbo-2024-04-09 | 4 | $10.00 |
|
|
195
|
+
| gpt-4.1 | 4 | $2.00 |
|
|
121
196
|
| gpt-4.1-mini | 4 | $0.40 |
|
|
122
197
|
| gpt-4.1-nano | 4 | $0.10 |
|
|
123
198
|
| gpt-4o | 4 | $2.50 |
|
|
199
|
+
| gpt-4o-2024-05-13 | 4 | $5.00 |
|
|
200
|
+
| gpt-4o-audio-preview | 4 | $2.50 |
|
|
124
201
|
| gpt-4o-mini | 4 | $0.15 |
|
|
202
|
+
| gpt-4o-mini-audio-preview | 4 | $0.15 |
|
|
203
|
+
| gpt-4o-mini-realtime-preview | 4 | $0.60 |
|
|
204
|
+
| gpt-4o-mini-search-preview | 4 | $0.15 |
|
|
205
|
+
| gpt-4o-realtime-preview | 4 | $5.00 |
|
|
206
|
+
| gpt-4o-search-preview | 4 | $2.50 |
|
|
207
|
+
| gpt-5 | 4 | $1.25 |
|
|
208
|
+
| gpt-5-chat-latest | 4 | $1.25 |
|
|
209
|
+
| gpt-5-codex | 4 | $1.25 |
|
|
125
210
|
| gpt-5-mini | 4 | $0.25 |
|
|
211
|
+
| gpt-5-nano | 4 | $0.05 |
|
|
212
|
+
| gpt-5-pro | 4 | $15.00 |
|
|
213
|
+
| gpt-5-search-api | 4 | $1.25 |
|
|
214
|
+
| gpt-5.1 | 4 | $1.25 |
|
|
215
|
+
| gpt-5.1-chat-latest | 4 | $1.25 |
|
|
216
|
+
| gpt-5.1-codex | 4 | $1.25 |
|
|
217
|
+
| gpt-5.1-codex-max | 4 | $1.25 |
|
|
218
|
+
| gpt-5.1-codex-mini | 4 | $0.25 |
|
|
126
219
|
| gpt-5.2 | 4 | $1.75 |
|
|
220
|
+
| gpt-5.2-chat-latest | 4 | $1.75 |
|
|
221
|
+
| gpt-5.2-codex | 4 | $1.75 |
|
|
127
222
|
| gpt-5.2-pro | 4 | $21.00 |
|
|
223
|
+
| gpt-audio | 4 | $2.50 |
|
|
224
|
+
| gpt-audio-mini | 4 | $0.60 |
|
|
225
|
+
| gpt-image-1 | 4 | $5.00 |
|
|
226
|
+
| gpt-image-1-mini | 4 | $2.00 |
|
|
227
|
+
| gpt-image-1.5 | 4 | $5.00 |
|
|
128
228
|
| gpt-realtime | 4 | $4.00 |
|
|
129
229
|
| gpt-realtime-mini | 4 | $0.60 |
|
|
130
230
|
| o1 | 4 | $15.00 |
|
|
231
|
+
| o1-mini | 4 | $1.10 |
|
|
131
232
|
| o1-pro | 4 | $150.00 |
|
|
132
233
|
| o3 | 4 | $2.00 |
|
|
133
|
-
|
|
|
234
|
+
| o3-deep-research | 4 | $10.00 |
|
|
235
|
+
| o3-mini | 4 | $1.10 |
|
|
236
|
+
| o3-pro | 4 | $20.00 |
|
|
237
|
+
| o4-mini | 4 | $1.10 |
|
|
238
|
+
| o4-mini-deep-research | 4 | $2.00 |
|
|
134
239
|
|
|
135
240
|
### Anthropic Claude Models
|
|
136
241
|
|
|
@@ -164,13 +269,13 @@ This package counts Unicode code points, not UTF-16 code units. This means:
|
|
|
164
269
|
| gemini-3-flash | 4 | $0.50 |
|
|
165
270
|
| gemini-3-pro | 4 | $2.00 |
|
|
166
271
|
|
|
167
|
-
*Last updated:
|
|
272
|
+
*Last updated: 2026-01-14*
|
|
168
273
|
<!-- SUPPORTED_MODELS_END -->
|
|
169
274
|
|
|
170
275
|
## Pricing Updates
|
|
171
276
|
|
|
172
277
|
Model pricing is automatically updated weekly via GitHub Actions. The update script fetches the latest prices directly from:
|
|
173
|
-
- [OpenAI Pricing](https://openai.com/
|
|
278
|
+
- [OpenAI Pricing](https://platform.openai.com/docs/pricing)
|
|
174
279
|
- [Anthropic Pricing](https://www.anthropic.com/pricing)
|
|
175
280
|
- [Google AI Pricing](https://ai.google.dev/gemini-api/docs/pricing)
|
|
176
281
|
|
|
@@ -178,7 +283,7 @@ You can check when prices were last updated:
|
|
|
178
283
|
|
|
179
284
|
```typescript
|
|
180
285
|
import { LAST_UPDATED } from 'ai-token-estimator';
|
|
181
|
-
console.log(LAST_UPDATED); // '
|
|
286
|
+
console.log(LAST_UPDATED); // e.g. '2026-01-14'
|
|
182
287
|
```
|
|
183
288
|
|
|
184
289
|
## License
|
package/dist/index.cjs
CHANGED
|
@@ -22,6 +22,9 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
24
24
|
LAST_UPDATED: () => LAST_UPDATED,
|
|
25
|
+
countTokens: () => countTokens,
|
|
26
|
+
decode: () => decode,
|
|
27
|
+
encode: () => encode,
|
|
25
28
|
estimate: () => estimate,
|
|
26
29
|
getAvailableModels: () => getAvailableModels,
|
|
27
30
|
getModelConfig: () => getModelConfig
|
|
@@ -29,44 +32,224 @@ __export(index_exports, {
|
|
|
29
32
|
module.exports = __toCommonJS(index_exports);
|
|
30
33
|
|
|
31
34
|
// src/models.ts
|
|
32
|
-
var LAST_UPDATED = "
|
|
35
|
+
var LAST_UPDATED = "2026-01-14";
|
|
33
36
|
var models = {
|
|
34
37
|
// ===================
|
|
35
38
|
// OpenAI Models
|
|
36
39
|
// ===================
|
|
37
40
|
// OpenAI uses ~4 chars per token for English text
|
|
38
|
-
"
|
|
41
|
+
"babbage-002": {
|
|
42
|
+
charsPerToken: 4,
|
|
43
|
+
inputCostPerMillion: 0.4
|
|
44
|
+
},
|
|
45
|
+
"chatgpt-4o-latest": {
|
|
46
|
+
charsPerToken: 4,
|
|
47
|
+
inputCostPerMillion: 5
|
|
48
|
+
},
|
|
49
|
+
"chatgpt-image-latest": {
|
|
50
|
+
charsPerToken: 4,
|
|
51
|
+
inputCostPerMillion: 5
|
|
52
|
+
},
|
|
53
|
+
"codex-mini-latest": {
|
|
54
|
+
charsPerToken: 4,
|
|
55
|
+
inputCostPerMillion: 1.5
|
|
56
|
+
},
|
|
57
|
+
"computer-use-preview": {
|
|
58
|
+
charsPerToken: 4,
|
|
59
|
+
inputCostPerMillion: 3
|
|
60
|
+
},
|
|
61
|
+
"davinci-002": {
|
|
62
|
+
charsPerToken: 4,
|
|
63
|
+
inputCostPerMillion: 2
|
|
64
|
+
},
|
|
65
|
+
"gpt-3.5-0301": {
|
|
66
|
+
charsPerToken: 4,
|
|
67
|
+
inputCostPerMillion: 1.5
|
|
68
|
+
},
|
|
69
|
+
"gpt-3.5-turbo": {
|
|
70
|
+
charsPerToken: 4,
|
|
71
|
+
inputCostPerMillion: 0.5
|
|
72
|
+
},
|
|
73
|
+
"gpt-3.5-turbo-0125": {
|
|
74
|
+
charsPerToken: 4,
|
|
75
|
+
inputCostPerMillion: 0.5
|
|
76
|
+
},
|
|
77
|
+
"gpt-3.5-turbo-0613": {
|
|
78
|
+
charsPerToken: 4,
|
|
79
|
+
inputCostPerMillion: 1.5
|
|
80
|
+
},
|
|
81
|
+
"gpt-3.5-turbo-1106": {
|
|
82
|
+
charsPerToken: 4,
|
|
83
|
+
inputCostPerMillion: 1
|
|
84
|
+
},
|
|
85
|
+
"gpt-3.5-turbo-16k-0613": {
|
|
39
86
|
charsPerToken: 4,
|
|
40
87
|
inputCostPerMillion: 3
|
|
41
88
|
},
|
|
89
|
+
"gpt-3.5-turbo-instruct": {
|
|
90
|
+
charsPerToken: 4,
|
|
91
|
+
inputCostPerMillion: 1.5
|
|
92
|
+
},
|
|
93
|
+
"gpt-4-0125-preview": {
|
|
94
|
+
charsPerToken: 4,
|
|
95
|
+
inputCostPerMillion: 10
|
|
96
|
+
},
|
|
97
|
+
"gpt-4-0314": {
|
|
98
|
+
charsPerToken: 4,
|
|
99
|
+
inputCostPerMillion: 30
|
|
100
|
+
},
|
|
101
|
+
"gpt-4-0613": {
|
|
102
|
+
charsPerToken: 4,
|
|
103
|
+
inputCostPerMillion: 30
|
|
104
|
+
},
|
|
105
|
+
"gpt-4-1106-preview": {
|
|
106
|
+
charsPerToken: 4,
|
|
107
|
+
inputCostPerMillion: 10
|
|
108
|
+
},
|
|
109
|
+
"gpt-4-1106-vision-preview": {
|
|
110
|
+
charsPerToken: 4,
|
|
111
|
+
inputCostPerMillion: 10
|
|
112
|
+
},
|
|
113
|
+
"gpt-4-32k": {
|
|
114
|
+
charsPerToken: 4,
|
|
115
|
+
inputCostPerMillion: 60
|
|
116
|
+
},
|
|
117
|
+
"gpt-4-turbo-2024-04-09": {
|
|
118
|
+
charsPerToken: 4,
|
|
119
|
+
inputCostPerMillion: 10
|
|
120
|
+
},
|
|
121
|
+
"gpt-4.1": {
|
|
122
|
+
charsPerToken: 4,
|
|
123
|
+
inputCostPerMillion: 2
|
|
124
|
+
},
|
|
42
125
|
"gpt-4.1-mini": {
|
|
43
126
|
charsPerToken: 4,
|
|
44
|
-
inputCostPerMillion: 0.
|
|
127
|
+
inputCostPerMillion: 0.4
|
|
45
128
|
},
|
|
46
129
|
"gpt-4.1-nano": {
|
|
47
130
|
charsPerToken: 4,
|
|
48
|
-
inputCostPerMillion: 0.
|
|
131
|
+
inputCostPerMillion: 0.1
|
|
49
132
|
},
|
|
50
133
|
"gpt-4o": {
|
|
51
134
|
charsPerToken: 4,
|
|
52
135
|
inputCostPerMillion: 2.5
|
|
53
136
|
},
|
|
137
|
+
"gpt-4o-2024-05-13": {
|
|
138
|
+
charsPerToken: 4,
|
|
139
|
+
inputCostPerMillion: 5
|
|
140
|
+
},
|
|
141
|
+
"gpt-4o-audio-preview": {
|
|
142
|
+
charsPerToken: 4,
|
|
143
|
+
inputCostPerMillion: 2.5
|
|
144
|
+
},
|
|
54
145
|
"gpt-4o-mini": {
|
|
55
146
|
charsPerToken: 4,
|
|
56
147
|
inputCostPerMillion: 0.15
|
|
57
148
|
},
|
|
149
|
+
"gpt-4o-mini-audio-preview": {
|
|
150
|
+
charsPerToken: 4,
|
|
151
|
+
inputCostPerMillion: 0.15
|
|
152
|
+
},
|
|
153
|
+
"gpt-4o-mini-realtime-preview": {
|
|
154
|
+
charsPerToken: 4,
|
|
155
|
+
inputCostPerMillion: 0.6
|
|
156
|
+
},
|
|
157
|
+
"gpt-4o-mini-search-preview": {
|
|
158
|
+
charsPerToken: 4,
|
|
159
|
+
inputCostPerMillion: 0.15
|
|
160
|
+
},
|
|
161
|
+
"gpt-4o-realtime-preview": {
|
|
162
|
+
charsPerToken: 4,
|
|
163
|
+
inputCostPerMillion: 5
|
|
164
|
+
},
|
|
165
|
+
"gpt-4o-search-preview": {
|
|
166
|
+
charsPerToken: 4,
|
|
167
|
+
inputCostPerMillion: 2.5
|
|
168
|
+
},
|
|
169
|
+
"gpt-5": {
|
|
170
|
+
charsPerToken: 4,
|
|
171
|
+
inputCostPerMillion: 1.25
|
|
172
|
+
},
|
|
58
173
|
"gpt-5-mini": {
|
|
59
174
|
charsPerToken: 4,
|
|
60
175
|
inputCostPerMillion: 0.25
|
|
61
176
|
},
|
|
177
|
+
"gpt-5-nano": {
|
|
178
|
+
charsPerToken: 4,
|
|
179
|
+
inputCostPerMillion: 0.05
|
|
180
|
+
},
|
|
181
|
+
"gpt-5-pro": {
|
|
182
|
+
charsPerToken: 4,
|
|
183
|
+
inputCostPerMillion: 15
|
|
184
|
+
},
|
|
185
|
+
"gpt-5-search-api": {
|
|
186
|
+
charsPerToken: 4,
|
|
187
|
+
inputCostPerMillion: 1.25
|
|
188
|
+
},
|
|
189
|
+
"gpt-5.1": {
|
|
190
|
+
charsPerToken: 4,
|
|
191
|
+
inputCostPerMillion: 1.25
|
|
192
|
+
},
|
|
193
|
+
"gpt-5.1-chat-latest": {
|
|
194
|
+
charsPerToken: 4,
|
|
195
|
+
inputCostPerMillion: 1.25
|
|
196
|
+
},
|
|
197
|
+
"gpt-5.1-codex": {
|
|
198
|
+
charsPerToken: 4,
|
|
199
|
+
inputCostPerMillion: 1.25
|
|
200
|
+
},
|
|
201
|
+
"gpt-5.1-codex-max": {
|
|
202
|
+
charsPerToken: 4,
|
|
203
|
+
inputCostPerMillion: 1.25
|
|
204
|
+
},
|
|
205
|
+
"gpt-5.1-codex-mini": {
|
|
206
|
+
charsPerToken: 4,
|
|
207
|
+
inputCostPerMillion: 0.25
|
|
208
|
+
},
|
|
209
|
+
"gpt-5-chat-latest": {
|
|
210
|
+
charsPerToken: 4,
|
|
211
|
+
inputCostPerMillion: 1.25
|
|
212
|
+
},
|
|
213
|
+
"gpt-5-codex": {
|
|
214
|
+
charsPerToken: 4,
|
|
215
|
+
inputCostPerMillion: 1.25
|
|
216
|
+
},
|
|
62
217
|
"gpt-5.2": {
|
|
63
218
|
charsPerToken: 4,
|
|
64
219
|
inputCostPerMillion: 1.75
|
|
65
220
|
},
|
|
221
|
+
"gpt-5.2-chat-latest": {
|
|
222
|
+
charsPerToken: 4,
|
|
223
|
+
inputCostPerMillion: 1.75
|
|
224
|
+
},
|
|
225
|
+
"gpt-5.2-codex": {
|
|
226
|
+
charsPerToken: 4,
|
|
227
|
+
inputCostPerMillion: 1.75
|
|
228
|
+
},
|
|
66
229
|
"gpt-5.2-pro": {
|
|
67
230
|
charsPerToken: 4,
|
|
68
231
|
inputCostPerMillion: 21
|
|
69
232
|
},
|
|
233
|
+
"gpt-audio": {
|
|
234
|
+
charsPerToken: 4,
|
|
235
|
+
inputCostPerMillion: 2.5
|
|
236
|
+
},
|
|
237
|
+
"gpt-audio-mini": {
|
|
238
|
+
charsPerToken: 4,
|
|
239
|
+
inputCostPerMillion: 0.6
|
|
240
|
+
},
|
|
241
|
+
"gpt-image-1": {
|
|
242
|
+
charsPerToken: 4,
|
|
243
|
+
inputCostPerMillion: 5
|
|
244
|
+
},
|
|
245
|
+
"gpt-image-1-mini": {
|
|
246
|
+
charsPerToken: 4,
|
|
247
|
+
inputCostPerMillion: 2
|
|
248
|
+
},
|
|
249
|
+
"gpt-image-1.5": {
|
|
250
|
+
charsPerToken: 4,
|
|
251
|
+
inputCostPerMillion: 5
|
|
252
|
+
},
|
|
70
253
|
"gpt-realtime": {
|
|
71
254
|
charsPerToken: 4,
|
|
72
255
|
inputCostPerMillion: 4
|
|
@@ -79,6 +262,10 @@ var models = {
|
|
|
79
262
|
charsPerToken: 4,
|
|
80
263
|
inputCostPerMillion: 15
|
|
81
264
|
},
|
|
265
|
+
"o1-mini": {
|
|
266
|
+
charsPerToken: 4,
|
|
267
|
+
inputCostPerMillion: 1.1
|
|
268
|
+
},
|
|
82
269
|
"o1-pro": {
|
|
83
270
|
charsPerToken: 4,
|
|
84
271
|
inputCostPerMillion: 150
|
|
@@ -87,9 +274,25 @@ var models = {
|
|
|
87
274
|
charsPerToken: 4,
|
|
88
275
|
inputCostPerMillion: 2
|
|
89
276
|
},
|
|
277
|
+
"o3-deep-research": {
|
|
278
|
+
charsPerToken: 4,
|
|
279
|
+
inputCostPerMillion: 10
|
|
280
|
+
},
|
|
281
|
+
"o3-mini": {
|
|
282
|
+
charsPerToken: 4,
|
|
283
|
+
inputCostPerMillion: 1.1
|
|
284
|
+
},
|
|
285
|
+
"o3-pro": {
|
|
286
|
+
charsPerToken: 4,
|
|
287
|
+
inputCostPerMillion: 20
|
|
288
|
+
},
|
|
90
289
|
"o4-mini": {
|
|
91
290
|
charsPerToken: 4,
|
|
92
|
-
inputCostPerMillion:
|
|
291
|
+
inputCostPerMillion: 1.1
|
|
292
|
+
},
|
|
293
|
+
"o4-mini-deep-research": {
|
|
294
|
+
charsPerToken: 4,
|
|
295
|
+
inputCostPerMillion: 2
|
|
93
296
|
},
|
|
94
297
|
// ===================
|
|
95
298
|
// Anthropic Models
|
|
@@ -204,6 +407,79 @@ function getAvailableModels() {
|
|
|
204
407
|
return Object.keys(DEFAULT_MODELS);
|
|
205
408
|
}
|
|
206
409
|
|
|
410
|
+
// src/openai-bpe.ts
|
|
411
|
+
var import_node_module = require("module");
|
|
412
|
+
var import_constants = require("gpt-tokenizer/constants");
|
|
413
|
+
var import_mapping = require("gpt-tokenizer/mapping");
|
|
414
|
+
var import_meta = {};
|
|
415
|
+
var requireBase = typeof __filename === "string" && __filename.length > 0 ? __filename : import_meta.url;
|
|
416
|
+
var NODE_REQUIRE = (0, import_node_module.createRequire)(requireBase);
|
|
417
|
+
var ENCODING_MODULES = {
|
|
418
|
+
r50k_base: "gpt-tokenizer/cjs/encoding/r50k_base",
|
|
419
|
+
p50k_base: "gpt-tokenizer/cjs/encoding/p50k_base",
|
|
420
|
+
p50k_edit: "gpt-tokenizer/cjs/encoding/p50k_edit",
|
|
421
|
+
cl100k_base: "gpt-tokenizer/cjs/encoding/cl100k_base",
|
|
422
|
+
o200k_base: "gpt-tokenizer/cjs/encoding/o200k_base",
|
|
423
|
+
o200k_harmony: "gpt-tokenizer/cjs/encoding/o200k_harmony"
|
|
424
|
+
};
|
|
425
|
+
var encodingApiCache = /* @__PURE__ */ new Map();
|
|
426
|
+
function getEncodingApi(encoding) {
|
|
427
|
+
const cached = encodingApiCache.get(encoding);
|
|
428
|
+
if (cached) return cached;
|
|
429
|
+
const modulePath = ENCODING_MODULES[encoding];
|
|
430
|
+
const mod = NODE_REQUIRE(modulePath);
|
|
431
|
+
const api = { encode: mod.encode, decode: mod.decode };
|
|
432
|
+
encodingApiCache.set(encoding, api);
|
|
433
|
+
return api;
|
|
434
|
+
}
|
|
435
|
+
function resolveEncoding(selector) {
|
|
436
|
+
if (selector?.encoding) {
|
|
437
|
+
return selector.encoding;
|
|
438
|
+
}
|
|
439
|
+
const model = selector?.model?.trim();
|
|
440
|
+
if (model) {
|
|
441
|
+
const mapped = import_mapping.modelToEncodingMap[model];
|
|
442
|
+
if (mapped) {
|
|
443
|
+
return mapped;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return import_mapping.DEFAULT_ENCODING;
|
|
447
|
+
}
|
|
448
|
+
function getOpenAIEncoding(selector) {
|
|
449
|
+
return resolveEncoding(selector);
|
|
450
|
+
}
|
|
451
|
+
function toGptTokenizerEncodeOptions(allowSpecial) {
|
|
452
|
+
const mode = allowSpecial ?? "none_raise";
|
|
453
|
+
switch (mode) {
|
|
454
|
+
case "all":
|
|
455
|
+
return {
|
|
456
|
+
allowedSpecial: import_constants.ALL_SPECIAL_TOKENS,
|
|
457
|
+
disallowedSpecial: /* @__PURE__ */ new Set()
|
|
458
|
+
};
|
|
459
|
+
case "none":
|
|
460
|
+
return {
|
|
461
|
+
allowedSpecial: /* @__PURE__ */ new Set(),
|
|
462
|
+
disallowedSpecial: /* @__PURE__ */ new Set()
|
|
463
|
+
};
|
|
464
|
+
case "none_raise":
|
|
465
|
+
default:
|
|
466
|
+
return {
|
|
467
|
+
disallowedSpecial: import_constants.ALL_SPECIAL_TOKENS
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
function encode(text, options) {
|
|
472
|
+
const encoding = resolveEncoding(options);
|
|
473
|
+
const api = getEncodingApi(encoding);
|
|
474
|
+
const encodeOptions = toGptTokenizerEncodeOptions(options?.allowSpecial);
|
|
475
|
+
return api.encode(text, encodeOptions);
|
|
476
|
+
}
|
|
477
|
+
function decode(tokens, options) {
|
|
478
|
+
const encoding = resolveEncoding(options);
|
|
479
|
+
const api = getEncodingApi(encoding);
|
|
480
|
+
return api.decode(tokens);
|
|
481
|
+
}
|
|
482
|
+
|
|
207
483
|
// src/estimator.ts
|
|
208
484
|
function countCodePoints(text) {
|
|
209
485
|
let count = 0;
|
|
@@ -213,21 +489,43 @@ function countCodePoints(text) {
|
|
|
213
489
|
return count;
|
|
214
490
|
}
|
|
215
491
|
function estimate(input) {
|
|
216
|
-
const { text, model, rounding = "ceil" } = input;
|
|
492
|
+
const { text, model, rounding = "ceil", tokenizer = "heuristic" } = input;
|
|
217
493
|
const config = getModelConfig(model);
|
|
218
494
|
const characterCount = countCodePoints(text);
|
|
219
|
-
const
|
|
495
|
+
const isNonOpenAIModel2 = model.startsWith("claude-") || model.startsWith("gemini-");
|
|
220
496
|
let estimatedTokens;
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
estimatedTokens =
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
497
|
+
let tokenizerModeUsed = "heuristic";
|
|
498
|
+
let encodingUsed;
|
|
499
|
+
const shouldTryExact = tokenizer === "openai_exact" || tokenizer === "auto";
|
|
500
|
+
if (shouldTryExact && !isNonOpenAIModel2) {
|
|
501
|
+
try {
|
|
502
|
+
estimatedTokens = encode(text, { model, allowSpecial: "none" }).length;
|
|
503
|
+
tokenizerModeUsed = "openai_exact";
|
|
504
|
+
encodingUsed = getOpenAIEncoding({ model });
|
|
505
|
+
} catch (error) {
|
|
506
|
+
if (tokenizer === "openai_exact") {
|
|
507
|
+
throw error;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
} else if (tokenizer === "openai_exact" && isNonOpenAIModel2) {
|
|
511
|
+
throw new Error(
|
|
512
|
+
`Tokenizer mode "openai_exact" requested for non-OpenAI model: "${model}"`
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
if (estimatedTokens === void 0) {
|
|
516
|
+
const rawTokens = characterCount / config.charsPerToken;
|
|
517
|
+
switch (rounding) {
|
|
518
|
+
case "floor":
|
|
519
|
+
estimatedTokens = Math.floor(rawTokens);
|
|
520
|
+
break;
|
|
521
|
+
case "round":
|
|
522
|
+
estimatedTokens = Math.round(rawTokens);
|
|
523
|
+
break;
|
|
524
|
+
case "ceil":
|
|
525
|
+
default:
|
|
526
|
+
estimatedTokens = Math.ceil(rawTokens);
|
|
527
|
+
}
|
|
528
|
+
tokenizerModeUsed = "heuristic";
|
|
231
529
|
}
|
|
232
530
|
const estimatedInputCost = estimatedTokens * config.inputCostPerMillion / 1e6;
|
|
233
531
|
return {
|
|
@@ -235,13 +533,44 @@ function estimate(input) {
|
|
|
235
533
|
characterCount,
|
|
236
534
|
estimatedTokens,
|
|
237
535
|
estimatedInputCost,
|
|
238
|
-
charsPerToken: config.charsPerToken
|
|
536
|
+
charsPerToken: config.charsPerToken,
|
|
537
|
+
tokenizerMode: tokenizerModeUsed,
|
|
538
|
+
encodingUsed
|
|
239
539
|
};
|
|
240
540
|
}
|
|
541
|
+
|
|
542
|
+
// src/token-counter.ts
|
|
543
|
+
function isNonOpenAIModel(model) {
|
|
544
|
+
return model.startsWith("claude-") || model.startsWith("gemini-");
|
|
545
|
+
}
|
|
546
|
+
function countTokens(input) {
|
|
547
|
+
const { text, model } = input;
|
|
548
|
+
if (isNonOpenAIModel(model)) {
|
|
549
|
+
return {
|
|
550
|
+
tokens: estimate({ text, model }).estimatedTokens,
|
|
551
|
+
exact: false
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
try {
|
|
555
|
+
return {
|
|
556
|
+
tokens: encode(text, { model, allowSpecial: "none" }).length,
|
|
557
|
+
exact: true,
|
|
558
|
+
encoding: getOpenAIEncoding({ model })
|
|
559
|
+
};
|
|
560
|
+
} catch {
|
|
561
|
+
return {
|
|
562
|
+
tokens: estimate({ text, model }).estimatedTokens,
|
|
563
|
+
exact: false
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
}
|
|
241
567
|
// Annotate the CommonJS export names for ESM import in node:
|
|
242
568
|
0 && (module.exports = {
|
|
243
569
|
DEFAULT_MODELS,
|
|
244
570
|
LAST_UPDATED,
|
|
571
|
+
countTokens,
|
|
572
|
+
decode,
|
|
573
|
+
encode,
|
|
245
574
|
estimate,
|
|
246
575
|
getAvailableModels,
|
|
247
576
|
getModelConfig
|
package/dist/index.d.cts
CHANGED
|
@@ -7,6 +7,7 @@ interface ModelConfig {
|
|
|
7
7
|
/** Cost in USD per 1 million input tokens */
|
|
8
8
|
inputCostPerMillion: number;
|
|
9
9
|
}
|
|
10
|
+
type TokenizerMode = 'heuristic' | 'openai_exact' | 'auto';
|
|
10
11
|
/**
|
|
11
12
|
* Input parameters for the estimate function.
|
|
12
13
|
*/
|
|
@@ -17,6 +18,13 @@ interface EstimateInput {
|
|
|
17
18
|
model: string;
|
|
18
19
|
/** Rounding strategy for token count (default: 'ceil') */
|
|
19
20
|
rounding?: 'ceil' | 'round' | 'floor';
|
|
21
|
+
/**
|
|
22
|
+
* Token counting strategy.
|
|
23
|
+
* - `heuristic` (default): use chars-per-token ratios
|
|
24
|
+
* - `openai_exact`: use OpenAI BPE tokenization (throws if non-OpenAI model)
|
|
25
|
+
* - `auto`: use OpenAI BPE for OpenAI models, otherwise heuristic
|
|
26
|
+
*/
|
|
27
|
+
tokenizer?: TokenizerMode;
|
|
20
28
|
}
|
|
21
29
|
/**
|
|
22
30
|
* Output from the estimate function.
|
|
@@ -32,6 +40,10 @@ interface EstimateOutput {
|
|
|
32
40
|
estimatedInputCost: number;
|
|
33
41
|
/** The chars-per-token ratio used */
|
|
34
42
|
charsPerToken: number;
|
|
43
|
+
/** Which tokenizer strategy was used */
|
|
44
|
+
tokenizerMode?: TokenizerMode;
|
|
45
|
+
/** OpenAI encoding used when tokenizerMode is `openai_exact` */
|
|
46
|
+
encodingUsed?: string;
|
|
35
47
|
}
|
|
36
48
|
|
|
37
49
|
/**
|
|
@@ -57,16 +69,16 @@ declare function estimate(input: EstimateInput): EstimateOutput;
|
|
|
57
69
|
* Default model configurations.
|
|
58
70
|
*
|
|
59
71
|
* AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
|
|
60
|
-
* Last updated:
|
|
72
|
+
* Last updated: 2026-01-14
|
|
61
73
|
*
|
|
62
74
|
* Sources:
|
|
63
|
-
* - OpenAI: https://openai.com/
|
|
75
|
+
* - OpenAI: https://platform.openai.com/docs/pricing
|
|
64
76
|
* - Anthropic: https://www.anthropic.com/pricing
|
|
65
77
|
* - Google: https://ai.google.dev/gemini-api/docs/pricing
|
|
66
78
|
*
|
|
67
79
|
* This file is automatically updated weekly by GitHub Actions.
|
|
68
80
|
*/
|
|
69
|
-
declare const LAST_UPDATED = "
|
|
81
|
+
declare const LAST_UPDATED = "2026-01-14";
|
|
70
82
|
declare const DEFAULT_MODELS: Readonly<Record<string, Readonly<ModelConfig>>>;
|
|
71
83
|
/**
|
|
72
84
|
* Get configuration for a specific model.
|
|
@@ -81,4 +93,52 @@ declare function getModelConfig(model: string): ModelConfig;
|
|
|
81
93
|
*/
|
|
82
94
|
declare function getAvailableModels(): string[];
|
|
83
95
|
|
|
84
|
-
|
|
96
|
+
type OpenAIEncoding = 'r50k_base' | 'p50k_base' | 'p50k_edit' | 'cl100k_base' | 'o200k_base' | 'o200k_harmony';
|
|
97
|
+
type SpecialTokenHandling = 'all' | 'none' | 'none_raise';
|
|
98
|
+
interface EncodeOptions {
|
|
99
|
+
/**
|
|
100
|
+
* Explicit OpenAI encoding override.
|
|
101
|
+
* When provided, this takes precedence over `model`.
|
|
102
|
+
*/
|
|
103
|
+
encoding?: OpenAIEncoding;
|
|
104
|
+
/**
|
|
105
|
+
* OpenAI model ID used to select the appropriate encoding.
|
|
106
|
+
*/
|
|
107
|
+
model?: string;
|
|
108
|
+
/**
|
|
109
|
+
* How special tokens are handled.
|
|
110
|
+
* - `none_raise` (default): throw if special tokens appear
|
|
111
|
+
* - `none`: treat special tokens as regular text
|
|
112
|
+
* - `all`: allow special tokens and encode them as special token IDs
|
|
113
|
+
*/
|
|
114
|
+
allowSpecial?: SpecialTokenHandling;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Encode text into OpenAI token IDs using tiktoken-compatible BPE encoding.
|
|
118
|
+
*
|
|
119
|
+
* This is exact tokenization for OpenAI models (unlike heuristic estimators).
|
|
120
|
+
*/
|
|
121
|
+
declare function encode(text: string, options?: EncodeOptions): number[];
|
|
122
|
+
/**
|
|
123
|
+
* Decode OpenAI token IDs into text using tiktoken-compatible BPE encoding.
|
|
124
|
+
*/
|
|
125
|
+
declare function decode(tokens: Iterable<number>, options?: Pick<EncodeOptions, 'encoding' | 'model'>): string;
|
|
126
|
+
|
|
127
|
+
interface TokenCountInput {
|
|
128
|
+
text: string;
|
|
129
|
+
model: string;
|
|
130
|
+
}
|
|
131
|
+
interface TokenCountOutput {
|
|
132
|
+
tokens: number;
|
|
133
|
+
exact: boolean;
|
|
134
|
+
encoding?: OpenAIEncoding;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Count tokens for a given model.
|
|
138
|
+
*
|
|
139
|
+
* - OpenAI models: exact BPE tokenization
|
|
140
|
+
* - Other providers: heuristic estimate (chars-per-token)
|
|
141
|
+
*/
|
|
142
|
+
declare function countTokens(input: TokenCountInput): TokenCountOutput;
|
|
143
|
+
|
|
144
|
+
export { DEFAULT_MODELS, type EncodeOptions, type EstimateInput, type EstimateOutput, LAST_UPDATED, type ModelConfig, type OpenAIEncoding, type SpecialTokenHandling, type TokenCountInput, type TokenCountOutput, type TokenizerMode, countTokens, decode, encode, estimate, getAvailableModels, getModelConfig };
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ interface ModelConfig {
|
|
|
7
7
|
/** Cost in USD per 1 million input tokens */
|
|
8
8
|
inputCostPerMillion: number;
|
|
9
9
|
}
|
|
10
|
+
type TokenizerMode = 'heuristic' | 'openai_exact' | 'auto';
|
|
10
11
|
/**
|
|
11
12
|
* Input parameters for the estimate function.
|
|
12
13
|
*/
|
|
@@ -17,6 +18,13 @@ interface EstimateInput {
|
|
|
17
18
|
model: string;
|
|
18
19
|
/** Rounding strategy for token count (default: 'ceil') */
|
|
19
20
|
rounding?: 'ceil' | 'round' | 'floor';
|
|
21
|
+
/**
|
|
22
|
+
* Token counting strategy.
|
|
23
|
+
* - `heuristic` (default): use chars-per-token ratios
|
|
24
|
+
* - `openai_exact`: use OpenAI BPE tokenization (throws if non-OpenAI model)
|
|
25
|
+
* - `auto`: use OpenAI BPE for OpenAI models, otherwise heuristic
|
|
26
|
+
*/
|
|
27
|
+
tokenizer?: TokenizerMode;
|
|
20
28
|
}
|
|
21
29
|
/**
|
|
22
30
|
* Output from the estimate function.
|
|
@@ -32,6 +40,10 @@ interface EstimateOutput {
|
|
|
32
40
|
estimatedInputCost: number;
|
|
33
41
|
/** The chars-per-token ratio used */
|
|
34
42
|
charsPerToken: number;
|
|
43
|
+
/** Which tokenizer strategy was used */
|
|
44
|
+
tokenizerMode?: TokenizerMode;
|
|
45
|
+
/** OpenAI encoding used when tokenizerMode is `openai_exact` */
|
|
46
|
+
encodingUsed?: string;
|
|
35
47
|
}
|
|
36
48
|
|
|
37
49
|
/**
|
|
@@ -57,16 +69,16 @@ declare function estimate(input: EstimateInput): EstimateOutput;
|
|
|
57
69
|
* Default model configurations.
|
|
58
70
|
*
|
|
59
71
|
* AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
|
|
60
|
-
* Last updated:
|
|
72
|
+
* Last updated: 2026-01-14
|
|
61
73
|
*
|
|
62
74
|
* Sources:
|
|
63
|
-
* - OpenAI: https://openai.com/
|
|
75
|
+
* - OpenAI: https://platform.openai.com/docs/pricing
|
|
64
76
|
* - Anthropic: https://www.anthropic.com/pricing
|
|
65
77
|
* - Google: https://ai.google.dev/gemini-api/docs/pricing
|
|
66
78
|
*
|
|
67
79
|
* This file is automatically updated weekly by GitHub Actions.
|
|
68
80
|
*/
|
|
69
|
-
declare const LAST_UPDATED = "
|
|
81
|
+
declare const LAST_UPDATED = "2026-01-14";
|
|
70
82
|
declare const DEFAULT_MODELS: Readonly<Record<string, Readonly<ModelConfig>>>;
|
|
71
83
|
/**
|
|
72
84
|
* Get configuration for a specific model.
|
|
@@ -81,4 +93,52 @@ declare function getModelConfig(model: string): ModelConfig;
|
|
|
81
93
|
*/
|
|
82
94
|
declare function getAvailableModels(): string[];
|
|
83
95
|
|
|
84
|
-
|
|
96
|
+
type OpenAIEncoding = 'r50k_base' | 'p50k_base' | 'p50k_edit' | 'cl100k_base' | 'o200k_base' | 'o200k_harmony';
|
|
97
|
+
type SpecialTokenHandling = 'all' | 'none' | 'none_raise';
|
|
98
|
+
interface EncodeOptions {
|
|
99
|
+
/**
|
|
100
|
+
* Explicit OpenAI encoding override.
|
|
101
|
+
* When provided, this takes precedence over `model`.
|
|
102
|
+
*/
|
|
103
|
+
encoding?: OpenAIEncoding;
|
|
104
|
+
/**
|
|
105
|
+
* OpenAI model ID used to select the appropriate encoding.
|
|
106
|
+
*/
|
|
107
|
+
model?: string;
|
|
108
|
+
/**
|
|
109
|
+
* How special tokens are handled.
|
|
110
|
+
* - `none_raise` (default): throw if special tokens appear
|
|
111
|
+
* - `none`: treat special tokens as regular text
|
|
112
|
+
* - `all`: allow special tokens and encode them as special token IDs
|
|
113
|
+
*/
|
|
114
|
+
allowSpecial?: SpecialTokenHandling;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Encode text into OpenAI token IDs using tiktoken-compatible BPE encoding.
|
|
118
|
+
*
|
|
119
|
+
* This is exact tokenization for OpenAI models (unlike heuristic estimators).
|
|
120
|
+
*/
|
|
121
|
+
declare function encode(text: string, options?: EncodeOptions): number[];
|
|
122
|
+
/**
|
|
123
|
+
* Decode OpenAI token IDs into text using tiktoken-compatible BPE encoding.
|
|
124
|
+
*/
|
|
125
|
+
declare function decode(tokens: Iterable<number>, options?: Pick<EncodeOptions, 'encoding' | 'model'>): string;
|
|
126
|
+
|
|
127
|
+
interface TokenCountInput {
|
|
128
|
+
text: string;
|
|
129
|
+
model: string;
|
|
130
|
+
}
|
|
131
|
+
interface TokenCountOutput {
|
|
132
|
+
tokens: number;
|
|
133
|
+
exact: boolean;
|
|
134
|
+
encoding?: OpenAIEncoding;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Count tokens for a given model.
|
|
138
|
+
*
|
|
139
|
+
* - OpenAI models: exact BPE tokenization
|
|
140
|
+
* - Other providers: heuristic estimate (chars-per-token)
|
|
141
|
+
*/
|
|
142
|
+
declare function countTokens(input: TokenCountInput): TokenCountOutput;
|
|
143
|
+
|
|
144
|
+
export { DEFAULT_MODELS, type EncodeOptions, type EstimateInput, type EstimateOutput, LAST_UPDATED, type ModelConfig, type OpenAIEncoding, type SpecialTokenHandling, type TokenCountInput, type TokenCountOutput, type TokenizerMode, countTokens, decode, encode, estimate, getAvailableModels, getModelConfig };
|
package/dist/index.js
CHANGED
|
@@ -1,42 +1,222 @@
|
|
|
1
1
|
// src/models.ts
|
|
2
|
-
var LAST_UPDATED = "
|
|
2
|
+
var LAST_UPDATED = "2026-01-14";
|
|
3
3
|
var models = {
|
|
4
4
|
// ===================
|
|
5
5
|
// OpenAI Models
|
|
6
6
|
// ===================
|
|
7
7
|
// OpenAI uses ~4 chars per token for English text
|
|
8
|
-
"
|
|
8
|
+
"babbage-002": {
|
|
9
|
+
charsPerToken: 4,
|
|
10
|
+
inputCostPerMillion: 0.4
|
|
11
|
+
},
|
|
12
|
+
"chatgpt-4o-latest": {
|
|
13
|
+
charsPerToken: 4,
|
|
14
|
+
inputCostPerMillion: 5
|
|
15
|
+
},
|
|
16
|
+
"chatgpt-image-latest": {
|
|
17
|
+
charsPerToken: 4,
|
|
18
|
+
inputCostPerMillion: 5
|
|
19
|
+
},
|
|
20
|
+
"codex-mini-latest": {
|
|
21
|
+
charsPerToken: 4,
|
|
22
|
+
inputCostPerMillion: 1.5
|
|
23
|
+
},
|
|
24
|
+
"computer-use-preview": {
|
|
25
|
+
charsPerToken: 4,
|
|
26
|
+
inputCostPerMillion: 3
|
|
27
|
+
},
|
|
28
|
+
"davinci-002": {
|
|
29
|
+
charsPerToken: 4,
|
|
30
|
+
inputCostPerMillion: 2
|
|
31
|
+
},
|
|
32
|
+
"gpt-3.5-0301": {
|
|
33
|
+
charsPerToken: 4,
|
|
34
|
+
inputCostPerMillion: 1.5
|
|
35
|
+
},
|
|
36
|
+
"gpt-3.5-turbo": {
|
|
37
|
+
charsPerToken: 4,
|
|
38
|
+
inputCostPerMillion: 0.5
|
|
39
|
+
},
|
|
40
|
+
"gpt-3.5-turbo-0125": {
|
|
41
|
+
charsPerToken: 4,
|
|
42
|
+
inputCostPerMillion: 0.5
|
|
43
|
+
},
|
|
44
|
+
"gpt-3.5-turbo-0613": {
|
|
45
|
+
charsPerToken: 4,
|
|
46
|
+
inputCostPerMillion: 1.5
|
|
47
|
+
},
|
|
48
|
+
"gpt-3.5-turbo-1106": {
|
|
49
|
+
charsPerToken: 4,
|
|
50
|
+
inputCostPerMillion: 1
|
|
51
|
+
},
|
|
52
|
+
"gpt-3.5-turbo-16k-0613": {
|
|
9
53
|
charsPerToken: 4,
|
|
10
54
|
inputCostPerMillion: 3
|
|
11
55
|
},
|
|
56
|
+
"gpt-3.5-turbo-instruct": {
|
|
57
|
+
charsPerToken: 4,
|
|
58
|
+
inputCostPerMillion: 1.5
|
|
59
|
+
},
|
|
60
|
+
"gpt-4-0125-preview": {
|
|
61
|
+
charsPerToken: 4,
|
|
62
|
+
inputCostPerMillion: 10
|
|
63
|
+
},
|
|
64
|
+
"gpt-4-0314": {
|
|
65
|
+
charsPerToken: 4,
|
|
66
|
+
inputCostPerMillion: 30
|
|
67
|
+
},
|
|
68
|
+
"gpt-4-0613": {
|
|
69
|
+
charsPerToken: 4,
|
|
70
|
+
inputCostPerMillion: 30
|
|
71
|
+
},
|
|
72
|
+
"gpt-4-1106-preview": {
|
|
73
|
+
charsPerToken: 4,
|
|
74
|
+
inputCostPerMillion: 10
|
|
75
|
+
},
|
|
76
|
+
"gpt-4-1106-vision-preview": {
|
|
77
|
+
charsPerToken: 4,
|
|
78
|
+
inputCostPerMillion: 10
|
|
79
|
+
},
|
|
80
|
+
"gpt-4-32k": {
|
|
81
|
+
charsPerToken: 4,
|
|
82
|
+
inputCostPerMillion: 60
|
|
83
|
+
},
|
|
84
|
+
"gpt-4-turbo-2024-04-09": {
|
|
85
|
+
charsPerToken: 4,
|
|
86
|
+
inputCostPerMillion: 10
|
|
87
|
+
},
|
|
88
|
+
"gpt-4.1": {
|
|
89
|
+
charsPerToken: 4,
|
|
90
|
+
inputCostPerMillion: 2
|
|
91
|
+
},
|
|
12
92
|
"gpt-4.1-mini": {
|
|
13
93
|
charsPerToken: 4,
|
|
14
|
-
inputCostPerMillion: 0.
|
|
94
|
+
inputCostPerMillion: 0.4
|
|
15
95
|
},
|
|
16
96
|
"gpt-4.1-nano": {
|
|
17
97
|
charsPerToken: 4,
|
|
18
|
-
inputCostPerMillion: 0.
|
|
98
|
+
inputCostPerMillion: 0.1
|
|
19
99
|
},
|
|
20
100
|
"gpt-4o": {
|
|
21
101
|
charsPerToken: 4,
|
|
22
102
|
inputCostPerMillion: 2.5
|
|
23
103
|
},
|
|
104
|
+
"gpt-4o-2024-05-13": {
|
|
105
|
+
charsPerToken: 4,
|
|
106
|
+
inputCostPerMillion: 5
|
|
107
|
+
},
|
|
108
|
+
"gpt-4o-audio-preview": {
|
|
109
|
+
charsPerToken: 4,
|
|
110
|
+
inputCostPerMillion: 2.5
|
|
111
|
+
},
|
|
24
112
|
"gpt-4o-mini": {
|
|
25
113
|
charsPerToken: 4,
|
|
26
114
|
inputCostPerMillion: 0.15
|
|
27
115
|
},
|
|
116
|
+
"gpt-4o-mini-audio-preview": {
|
|
117
|
+
charsPerToken: 4,
|
|
118
|
+
inputCostPerMillion: 0.15
|
|
119
|
+
},
|
|
120
|
+
"gpt-4o-mini-realtime-preview": {
|
|
121
|
+
charsPerToken: 4,
|
|
122
|
+
inputCostPerMillion: 0.6
|
|
123
|
+
},
|
|
124
|
+
"gpt-4o-mini-search-preview": {
|
|
125
|
+
charsPerToken: 4,
|
|
126
|
+
inputCostPerMillion: 0.15
|
|
127
|
+
},
|
|
128
|
+
"gpt-4o-realtime-preview": {
|
|
129
|
+
charsPerToken: 4,
|
|
130
|
+
inputCostPerMillion: 5
|
|
131
|
+
},
|
|
132
|
+
"gpt-4o-search-preview": {
|
|
133
|
+
charsPerToken: 4,
|
|
134
|
+
inputCostPerMillion: 2.5
|
|
135
|
+
},
|
|
136
|
+
"gpt-5": {
|
|
137
|
+
charsPerToken: 4,
|
|
138
|
+
inputCostPerMillion: 1.25
|
|
139
|
+
},
|
|
28
140
|
"gpt-5-mini": {
|
|
29
141
|
charsPerToken: 4,
|
|
30
142
|
inputCostPerMillion: 0.25
|
|
31
143
|
},
|
|
144
|
+
"gpt-5-nano": {
|
|
145
|
+
charsPerToken: 4,
|
|
146
|
+
inputCostPerMillion: 0.05
|
|
147
|
+
},
|
|
148
|
+
"gpt-5-pro": {
|
|
149
|
+
charsPerToken: 4,
|
|
150
|
+
inputCostPerMillion: 15
|
|
151
|
+
},
|
|
152
|
+
"gpt-5-search-api": {
|
|
153
|
+
charsPerToken: 4,
|
|
154
|
+
inputCostPerMillion: 1.25
|
|
155
|
+
},
|
|
156
|
+
"gpt-5.1": {
|
|
157
|
+
charsPerToken: 4,
|
|
158
|
+
inputCostPerMillion: 1.25
|
|
159
|
+
},
|
|
160
|
+
"gpt-5.1-chat-latest": {
|
|
161
|
+
charsPerToken: 4,
|
|
162
|
+
inputCostPerMillion: 1.25
|
|
163
|
+
},
|
|
164
|
+
"gpt-5.1-codex": {
|
|
165
|
+
charsPerToken: 4,
|
|
166
|
+
inputCostPerMillion: 1.25
|
|
167
|
+
},
|
|
168
|
+
"gpt-5.1-codex-max": {
|
|
169
|
+
charsPerToken: 4,
|
|
170
|
+
inputCostPerMillion: 1.25
|
|
171
|
+
},
|
|
172
|
+
"gpt-5.1-codex-mini": {
|
|
173
|
+
charsPerToken: 4,
|
|
174
|
+
inputCostPerMillion: 0.25
|
|
175
|
+
},
|
|
176
|
+
"gpt-5-chat-latest": {
|
|
177
|
+
charsPerToken: 4,
|
|
178
|
+
inputCostPerMillion: 1.25
|
|
179
|
+
},
|
|
180
|
+
"gpt-5-codex": {
|
|
181
|
+
charsPerToken: 4,
|
|
182
|
+
inputCostPerMillion: 1.25
|
|
183
|
+
},
|
|
32
184
|
"gpt-5.2": {
|
|
33
185
|
charsPerToken: 4,
|
|
34
186
|
inputCostPerMillion: 1.75
|
|
35
187
|
},
|
|
188
|
+
"gpt-5.2-chat-latest": {
|
|
189
|
+
charsPerToken: 4,
|
|
190
|
+
inputCostPerMillion: 1.75
|
|
191
|
+
},
|
|
192
|
+
"gpt-5.2-codex": {
|
|
193
|
+
charsPerToken: 4,
|
|
194
|
+
inputCostPerMillion: 1.75
|
|
195
|
+
},
|
|
36
196
|
"gpt-5.2-pro": {
|
|
37
197
|
charsPerToken: 4,
|
|
38
198
|
inputCostPerMillion: 21
|
|
39
199
|
},
|
|
200
|
+
"gpt-audio": {
|
|
201
|
+
charsPerToken: 4,
|
|
202
|
+
inputCostPerMillion: 2.5
|
|
203
|
+
},
|
|
204
|
+
"gpt-audio-mini": {
|
|
205
|
+
charsPerToken: 4,
|
|
206
|
+
inputCostPerMillion: 0.6
|
|
207
|
+
},
|
|
208
|
+
"gpt-image-1": {
|
|
209
|
+
charsPerToken: 4,
|
|
210
|
+
inputCostPerMillion: 5
|
|
211
|
+
},
|
|
212
|
+
"gpt-image-1-mini": {
|
|
213
|
+
charsPerToken: 4,
|
|
214
|
+
inputCostPerMillion: 2
|
|
215
|
+
},
|
|
216
|
+
"gpt-image-1.5": {
|
|
217
|
+
charsPerToken: 4,
|
|
218
|
+
inputCostPerMillion: 5
|
|
219
|
+
},
|
|
40
220
|
"gpt-realtime": {
|
|
41
221
|
charsPerToken: 4,
|
|
42
222
|
inputCostPerMillion: 4
|
|
@@ -49,6 +229,10 @@ var models = {
|
|
|
49
229
|
charsPerToken: 4,
|
|
50
230
|
inputCostPerMillion: 15
|
|
51
231
|
},
|
|
232
|
+
"o1-mini": {
|
|
233
|
+
charsPerToken: 4,
|
|
234
|
+
inputCostPerMillion: 1.1
|
|
235
|
+
},
|
|
52
236
|
"o1-pro": {
|
|
53
237
|
charsPerToken: 4,
|
|
54
238
|
inputCostPerMillion: 150
|
|
@@ -57,9 +241,25 @@ var models = {
|
|
|
57
241
|
charsPerToken: 4,
|
|
58
242
|
inputCostPerMillion: 2
|
|
59
243
|
},
|
|
244
|
+
"o3-deep-research": {
|
|
245
|
+
charsPerToken: 4,
|
|
246
|
+
inputCostPerMillion: 10
|
|
247
|
+
},
|
|
248
|
+
"o3-mini": {
|
|
249
|
+
charsPerToken: 4,
|
|
250
|
+
inputCostPerMillion: 1.1
|
|
251
|
+
},
|
|
252
|
+
"o3-pro": {
|
|
253
|
+
charsPerToken: 4,
|
|
254
|
+
inputCostPerMillion: 20
|
|
255
|
+
},
|
|
60
256
|
"o4-mini": {
|
|
61
257
|
charsPerToken: 4,
|
|
62
|
-
inputCostPerMillion:
|
|
258
|
+
inputCostPerMillion: 1.1
|
|
259
|
+
},
|
|
260
|
+
"o4-mini-deep-research": {
|
|
261
|
+
charsPerToken: 4,
|
|
262
|
+
inputCostPerMillion: 2
|
|
63
263
|
},
|
|
64
264
|
// ===================
|
|
65
265
|
// Anthropic Models
|
|
@@ -174,6 +374,78 @@ function getAvailableModels() {
|
|
|
174
374
|
return Object.keys(DEFAULT_MODELS);
|
|
175
375
|
}
|
|
176
376
|
|
|
377
|
+
// src/openai-bpe.ts
|
|
378
|
+
import { createRequire } from "module";
|
|
379
|
+
import { ALL_SPECIAL_TOKENS } from "gpt-tokenizer/constants";
|
|
380
|
+
import { DEFAULT_ENCODING, modelToEncodingMap } from "gpt-tokenizer/mapping";
|
|
381
|
+
var requireBase = typeof __filename === "string" && __filename.length > 0 ? __filename : import.meta.url;
|
|
382
|
+
var NODE_REQUIRE = createRequire(requireBase);
|
|
383
|
+
var ENCODING_MODULES = {
|
|
384
|
+
r50k_base: "gpt-tokenizer/cjs/encoding/r50k_base",
|
|
385
|
+
p50k_base: "gpt-tokenizer/cjs/encoding/p50k_base",
|
|
386
|
+
p50k_edit: "gpt-tokenizer/cjs/encoding/p50k_edit",
|
|
387
|
+
cl100k_base: "gpt-tokenizer/cjs/encoding/cl100k_base",
|
|
388
|
+
o200k_base: "gpt-tokenizer/cjs/encoding/o200k_base",
|
|
389
|
+
o200k_harmony: "gpt-tokenizer/cjs/encoding/o200k_harmony"
|
|
390
|
+
};
|
|
391
|
+
var encodingApiCache = /* @__PURE__ */ new Map();
|
|
392
|
+
function getEncodingApi(encoding) {
|
|
393
|
+
const cached = encodingApiCache.get(encoding);
|
|
394
|
+
if (cached) return cached;
|
|
395
|
+
const modulePath = ENCODING_MODULES[encoding];
|
|
396
|
+
const mod = NODE_REQUIRE(modulePath);
|
|
397
|
+
const api = { encode: mod.encode, decode: mod.decode };
|
|
398
|
+
encodingApiCache.set(encoding, api);
|
|
399
|
+
return api;
|
|
400
|
+
}
|
|
401
|
+
function resolveEncoding(selector) {
|
|
402
|
+
if (selector?.encoding) {
|
|
403
|
+
return selector.encoding;
|
|
404
|
+
}
|
|
405
|
+
const model = selector?.model?.trim();
|
|
406
|
+
if (model) {
|
|
407
|
+
const mapped = modelToEncodingMap[model];
|
|
408
|
+
if (mapped) {
|
|
409
|
+
return mapped;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return DEFAULT_ENCODING;
|
|
413
|
+
}
|
|
414
|
+
function getOpenAIEncoding(selector) {
|
|
415
|
+
return resolveEncoding(selector);
|
|
416
|
+
}
|
|
417
|
+
function toGptTokenizerEncodeOptions(allowSpecial) {
|
|
418
|
+
const mode = allowSpecial ?? "none_raise";
|
|
419
|
+
switch (mode) {
|
|
420
|
+
case "all":
|
|
421
|
+
return {
|
|
422
|
+
allowedSpecial: ALL_SPECIAL_TOKENS,
|
|
423
|
+
disallowedSpecial: /* @__PURE__ */ new Set()
|
|
424
|
+
};
|
|
425
|
+
case "none":
|
|
426
|
+
return {
|
|
427
|
+
allowedSpecial: /* @__PURE__ */ new Set(),
|
|
428
|
+
disallowedSpecial: /* @__PURE__ */ new Set()
|
|
429
|
+
};
|
|
430
|
+
case "none_raise":
|
|
431
|
+
default:
|
|
432
|
+
return {
|
|
433
|
+
disallowedSpecial: ALL_SPECIAL_TOKENS
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
function encode(text, options) {
|
|
438
|
+
const encoding = resolveEncoding(options);
|
|
439
|
+
const api = getEncodingApi(encoding);
|
|
440
|
+
const encodeOptions = toGptTokenizerEncodeOptions(options?.allowSpecial);
|
|
441
|
+
return api.encode(text, encodeOptions);
|
|
442
|
+
}
|
|
443
|
+
function decode(tokens, options) {
|
|
444
|
+
const encoding = resolveEncoding(options);
|
|
445
|
+
const api = getEncodingApi(encoding);
|
|
446
|
+
return api.decode(tokens);
|
|
447
|
+
}
|
|
448
|
+
|
|
177
449
|
// src/estimator.ts
|
|
178
450
|
function countCodePoints(text) {
|
|
179
451
|
let count = 0;
|
|
@@ -183,21 +455,43 @@ function countCodePoints(text) {
|
|
|
183
455
|
return count;
|
|
184
456
|
}
|
|
185
457
|
function estimate(input) {
|
|
186
|
-
const { text, model, rounding = "ceil" } = input;
|
|
458
|
+
const { text, model, rounding = "ceil", tokenizer = "heuristic" } = input;
|
|
187
459
|
const config = getModelConfig(model);
|
|
188
460
|
const characterCount = countCodePoints(text);
|
|
189
|
-
const
|
|
461
|
+
const isNonOpenAIModel2 = model.startsWith("claude-") || model.startsWith("gemini-");
|
|
190
462
|
let estimatedTokens;
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
estimatedTokens =
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
463
|
+
let tokenizerModeUsed = "heuristic";
|
|
464
|
+
let encodingUsed;
|
|
465
|
+
const shouldTryExact = tokenizer === "openai_exact" || tokenizer === "auto";
|
|
466
|
+
if (shouldTryExact && !isNonOpenAIModel2) {
|
|
467
|
+
try {
|
|
468
|
+
estimatedTokens = encode(text, { model, allowSpecial: "none" }).length;
|
|
469
|
+
tokenizerModeUsed = "openai_exact";
|
|
470
|
+
encodingUsed = getOpenAIEncoding({ model });
|
|
471
|
+
} catch (error) {
|
|
472
|
+
if (tokenizer === "openai_exact") {
|
|
473
|
+
throw error;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
} else if (tokenizer === "openai_exact" && isNonOpenAIModel2) {
|
|
477
|
+
throw new Error(
|
|
478
|
+
`Tokenizer mode "openai_exact" requested for non-OpenAI model: "${model}"`
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
if (estimatedTokens === void 0) {
|
|
482
|
+
const rawTokens = characterCount / config.charsPerToken;
|
|
483
|
+
switch (rounding) {
|
|
484
|
+
case "floor":
|
|
485
|
+
estimatedTokens = Math.floor(rawTokens);
|
|
486
|
+
break;
|
|
487
|
+
case "round":
|
|
488
|
+
estimatedTokens = Math.round(rawTokens);
|
|
489
|
+
break;
|
|
490
|
+
case "ceil":
|
|
491
|
+
default:
|
|
492
|
+
estimatedTokens = Math.ceil(rawTokens);
|
|
493
|
+
}
|
|
494
|
+
tokenizerModeUsed = "heuristic";
|
|
201
495
|
}
|
|
202
496
|
const estimatedInputCost = estimatedTokens * config.inputCostPerMillion / 1e6;
|
|
203
497
|
return {
|
|
@@ -205,12 +499,43 @@ function estimate(input) {
|
|
|
205
499
|
characterCount,
|
|
206
500
|
estimatedTokens,
|
|
207
501
|
estimatedInputCost,
|
|
208
|
-
charsPerToken: config.charsPerToken
|
|
502
|
+
charsPerToken: config.charsPerToken,
|
|
503
|
+
tokenizerMode: tokenizerModeUsed,
|
|
504
|
+
encodingUsed
|
|
209
505
|
};
|
|
210
506
|
}
|
|
507
|
+
|
|
508
|
+
// src/token-counter.ts
|
|
509
|
+
function isNonOpenAIModel(model) {
|
|
510
|
+
return model.startsWith("claude-") || model.startsWith("gemini-");
|
|
511
|
+
}
|
|
512
|
+
function countTokens(input) {
|
|
513
|
+
const { text, model } = input;
|
|
514
|
+
if (isNonOpenAIModel(model)) {
|
|
515
|
+
return {
|
|
516
|
+
tokens: estimate({ text, model }).estimatedTokens,
|
|
517
|
+
exact: false
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
try {
|
|
521
|
+
return {
|
|
522
|
+
tokens: encode(text, { model, allowSpecial: "none" }).length,
|
|
523
|
+
exact: true,
|
|
524
|
+
encoding: getOpenAIEncoding({ model })
|
|
525
|
+
};
|
|
526
|
+
} catch {
|
|
527
|
+
return {
|
|
528
|
+
tokens: estimate({ text, model }).estimatedTokens,
|
|
529
|
+
exact: false
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
}
|
|
211
533
|
export {
|
|
212
534
|
DEFAULT_MODELS,
|
|
213
535
|
LAST_UPDATED,
|
|
536
|
+
countTokens,
|
|
537
|
+
decode,
|
|
538
|
+
encode,
|
|
214
539
|
estimate,
|
|
215
540
|
getAvailableModels,
|
|
216
541
|
getModelConfig
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-token-estimator",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Estimate token counts and costs for LLM API calls",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -23,13 +23,17 @@
|
|
|
23
23
|
"LICENSE",
|
|
24
24
|
"README.md"
|
|
25
25
|
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"gpt-tokenizer": "^3.4.0"
|
|
28
|
+
},
|
|
26
29
|
"scripts": {
|
|
27
30
|
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
28
31
|
"test": "vitest run",
|
|
29
32
|
"test:watch": "vitest",
|
|
30
33
|
"lint": "eslint src tests",
|
|
31
34
|
"prepublishOnly": "npm run lint && npm run test && npm run build",
|
|
32
|
-
"update-pricing": "tsx scripts/update-pricing.ts"
|
|
35
|
+
"update-pricing": "tsx scripts/update-pricing.ts",
|
|
36
|
+
"benchmark:tokenizer": "tsx benchmark/tokenizer.ts"
|
|
33
37
|
},
|
|
34
38
|
"keywords": [
|
|
35
39
|
"llm",
|