@ank1015/providers 0.0.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/README.md +453 -0
- package/biome.json +43 -0
- package/dist/agent/agent-loop.d.ts +5 -0
- package/dist/agent/agent-loop.d.ts.map +1 -0
- package/dist/agent/agent-loop.js +219 -0
- package/dist/agent/agent-loop.js.map +1 -0
- package/dist/agent/types.d.ts +67 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +3 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/models.d.ts +3 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.generated.d.ts +247 -0
- package/dist/models.generated.d.ts.map +1 -0
- package/dist/models.generated.js +315 -0
- package/dist/models.generated.js.map +1 -0
- package/dist/models.js +41 -0
- package/dist/models.js.map +1 -0
- package/dist/providers/convert.d.ts +6 -0
- package/dist/providers/convert.d.ts.map +1 -0
- package/dist/providers/convert.js +207 -0
- package/dist/providers/convert.js.map +1 -0
- package/dist/providers/google.d.ts +26 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +434 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/openai.d.ts +17 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +396 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/stream.d.ts +4 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +40 -0
- package/dist/stream.js.map +1 -0
- package/dist/test-google-agent-loop.d.ts +2 -0
- package/dist/test-google-agent-loop.d.ts.map +1 -0
- package/dist/test-google-agent-loop.js +186 -0
- package/dist/test-google-agent-loop.js.map +1 -0
- package/dist/test-google.d.ts +2 -0
- package/dist/test-google.d.ts.map +1 -0
- package/dist/test-google.js +41 -0
- package/dist/test-google.js.map +1 -0
- package/dist/types.d.ts +187 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/event-stream.d.ts +16 -0
- package/dist/utils/event-stream.d.ts.map +1 -0
- package/dist/utils/event-stream.js +61 -0
- package/dist/utils/event-stream.js.map +1 -0
- package/dist/utils/json-parse.d.ts +9 -0
- package/dist/utils/json-parse.d.ts.map +1 -0
- package/dist/utils/json-parse.js +32 -0
- package/dist/utils/json-parse.js.map +1 -0
- package/dist/utils/sanitize-unicode.d.ts +22 -0
- package/dist/utils/sanitize-unicode.d.ts.map +1 -0
- package/dist/utils/sanitize-unicode.js +29 -0
- package/dist/utils/sanitize-unicode.js.map +1 -0
- package/dist/utils/validation.d.ts +11 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +61 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +33 -0
- package/src/agent/agent-loop.ts +275 -0
- package/src/agent/types.ts +80 -0
- package/src/index.ts +72 -0
- package/src/models.generated.ts +314 -0
- package/src/models.ts +45 -0
- package/src/providers/convert.ts +222 -0
- package/src/providers/google.ts +496 -0
- package/src/providers/openai.ts +437 -0
- package/src/stream.ts +60 -0
- package/src/types.ts +198 -0
- package/src/utils/event-stream.ts +60 -0
- package/src/utils/json-parse.ts +28 -0
- package/src/utils/sanitize-unicode.ts +25 -0
- package/src/utils/validation.ts +69 -0
- package/test/core/agent-loop.test.ts +958 -0
- package/test/core/stream.test.ts +409 -0
- package/test/data/red-circle.png +0 -0
- package/test/data/superintelligentwill.pdf +0 -0
- package/test/edge-cases/general.test.ts +565 -0
- package/test/integration/e2e.test.ts +530 -0
- package/test/models/cost.test.ts +499 -0
- package/test/models/registry.test.ts +298 -0
- package/test/providers/convert.test.ts +846 -0
- package/test/providers/google-schema.test.ts +666 -0
- package/test/providers/google-stream.test.ts +369 -0
- package/test/providers/openai-stream.test.ts +251 -0
- package/test/utils/event-stream.test.ts +289 -0
- package/test/utils/json-parse.test.ts +344 -0
- package/test/utils/sanitize-unicode.test.ts +329 -0
- package/test/utils/validation.test.ts +614 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { calculateCost } from '../../src/models';
|
|
3
|
+
import { Model, Usage } from '../../src/types';
|
|
4
|
+
|
|
5
|
+
const mockModel: Model<'openai'> = {
|
|
6
|
+
id: 'test-model',
|
|
7
|
+
name: 'Test Model',
|
|
8
|
+
api: 'openai',
|
|
9
|
+
baseUrl: 'https://api.openai.com',
|
|
10
|
+
reasoning: false,
|
|
11
|
+
input: ['text'],
|
|
12
|
+
cost: {
|
|
13
|
+
input: 0.15, // $0.15 per million tokens
|
|
14
|
+
output: 0.60, // $0.60 per million tokens
|
|
15
|
+
cacheRead: 0.015, // $0.015 per million tokens
|
|
16
|
+
cacheWrite: 0.30, // $0.30 per million tokens
|
|
17
|
+
},
|
|
18
|
+
contextWindow: 128000,
|
|
19
|
+
maxTokens: 4096,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe('calculateCost', () => {
|
|
23
|
+
describe('Basic calculations', () => {
|
|
24
|
+
it('should calculate input token cost', () => {
|
|
25
|
+
const usage: Usage = {
|
|
26
|
+
input: 1000,
|
|
27
|
+
output: 0,
|
|
28
|
+
cacheRead: 0,
|
|
29
|
+
cacheWrite: 0,
|
|
30
|
+
totalTokens: 1000,
|
|
31
|
+
cost: {
|
|
32
|
+
input: 0,
|
|
33
|
+
output: 0,
|
|
34
|
+
cacheRead: 0,
|
|
35
|
+
cacheWrite: 0,
|
|
36
|
+
total: 0,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
calculateCost(mockModel, usage);
|
|
41
|
+
|
|
42
|
+
// 1000 tokens * ($0.15 / 1,000,000) = $0.00015
|
|
43
|
+
expect(usage.cost.input).toBeCloseTo(0.00015, 10);
|
|
44
|
+
expect(usage.cost.total).toBeCloseTo(0.00015, 10);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should calculate output token cost', () => {
|
|
48
|
+
const usage: Usage = {
|
|
49
|
+
input: 0,
|
|
50
|
+
output: 500,
|
|
51
|
+
cacheRead: 0,
|
|
52
|
+
cacheWrite: 0,
|
|
53
|
+
totalTokens: 500,
|
|
54
|
+
cost: {
|
|
55
|
+
input: 0,
|
|
56
|
+
output: 0,
|
|
57
|
+
cacheRead: 0,
|
|
58
|
+
cacheWrite: 0,
|
|
59
|
+
total: 0,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
calculateCost(mockModel, usage);
|
|
64
|
+
|
|
65
|
+
// 500 tokens * ($0.60 / 1,000,000) = $0.0003
|
|
66
|
+
expect(usage.cost.output).toBeCloseTo(0.0003, 10);
|
|
67
|
+
expect(usage.cost.total).toBeCloseTo(0.0003, 10);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should calculate cache read token cost', () => {
|
|
71
|
+
const usage: Usage = {
|
|
72
|
+
input: 0,
|
|
73
|
+
output: 0,
|
|
74
|
+
cacheRead: 10000,
|
|
75
|
+
cacheWrite: 0,
|
|
76
|
+
totalTokens: 10000,
|
|
77
|
+
cost: {
|
|
78
|
+
input: 0,
|
|
79
|
+
output: 0,
|
|
80
|
+
cacheRead: 0,
|
|
81
|
+
cacheWrite: 0,
|
|
82
|
+
total: 0,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
calculateCost(mockModel, usage);
|
|
87
|
+
|
|
88
|
+
// 10000 tokens * ($0.015 / 1,000,000) = $0.00015
|
|
89
|
+
expect(usage.cost.cacheRead).toBeCloseTo(0.00015, 10);
|
|
90
|
+
expect(usage.cost.total).toBeCloseTo(0.00015, 10);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should calculate cache write token cost', () => {
|
|
94
|
+
const usage: Usage = {
|
|
95
|
+
input: 0,
|
|
96
|
+
output: 0,
|
|
97
|
+
cacheRead: 0,
|
|
98
|
+
cacheWrite: 5000,
|
|
99
|
+
totalTokens: 5000,
|
|
100
|
+
cost: {
|
|
101
|
+
input: 0,
|
|
102
|
+
output: 0,
|
|
103
|
+
cacheRead: 0,
|
|
104
|
+
cacheWrite: 0,
|
|
105
|
+
total: 0,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
calculateCost(mockModel, usage);
|
|
110
|
+
|
|
111
|
+
// 5000 tokens * ($0.30 / 1,000,000) = $0.0015
|
|
112
|
+
expect(usage.cost.cacheWrite).toBeCloseTo(0.0015, 10);
|
|
113
|
+
expect(usage.cost.total).toBeCloseTo(0.0015, 10);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('Combined calculations', () => {
|
|
118
|
+
it('should calculate total cost for all token types', () => {
|
|
119
|
+
const usage: Usage = {
|
|
120
|
+
input: 1000,
|
|
121
|
+
output: 500,
|
|
122
|
+
cacheRead: 2000,
|
|
123
|
+
cacheWrite: 1000,
|
|
124
|
+
totalTokens: 4500,
|
|
125
|
+
cost: {
|
|
126
|
+
input: 0,
|
|
127
|
+
output: 0,
|
|
128
|
+
cacheRead: 0,
|
|
129
|
+
cacheWrite: 0,
|
|
130
|
+
total: 0,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
calculateCost(mockModel, usage);
|
|
135
|
+
|
|
136
|
+
// Input: 1000 * 0.15 / 1M = 0.00015
|
|
137
|
+
// Output: 500 * 0.60 / 1M = 0.0003
|
|
138
|
+
// Cache Read: 2000 * 0.015 / 1M = 0.00003
|
|
139
|
+
// Cache Write: 1000 * 0.30 / 1M = 0.0003
|
|
140
|
+
// Total: 0.00015 + 0.0003 + 0.00003 + 0.0003 = 0.00078
|
|
141
|
+
expect(usage.cost.input).toBeCloseTo(0.00015, 10);
|
|
142
|
+
expect(usage.cost.output).toBeCloseTo(0.0003, 10);
|
|
143
|
+
expect(usage.cost.cacheRead).toBeCloseTo(0.00003, 10);
|
|
144
|
+
expect(usage.cost.cacheWrite).toBeCloseTo(0.0003, 10);
|
|
145
|
+
expect(usage.cost.total).toBeCloseTo(0.00078, 10);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should calculate cost for typical conversation', () => {
|
|
149
|
+
const usage: Usage = {
|
|
150
|
+
input: 5000,
|
|
151
|
+
output: 2000,
|
|
152
|
+
cacheRead: 0,
|
|
153
|
+
cacheWrite: 0,
|
|
154
|
+
totalTokens: 7000,
|
|
155
|
+
cost: {
|
|
156
|
+
input: 0,
|
|
157
|
+
output: 0,
|
|
158
|
+
cacheRead: 0,
|
|
159
|
+
cacheWrite: 0,
|
|
160
|
+
total: 0,
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
calculateCost(mockModel, usage);
|
|
165
|
+
|
|
166
|
+
expect(usage.cost.total).toBeCloseTo(0.00195, 10);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should calculate cost with caching enabled', () => {
|
|
170
|
+
const usage: Usage = {
|
|
171
|
+
input: 1000,
|
|
172
|
+
output: 500,
|
|
173
|
+
cacheRead: 10000, // Most context from cache
|
|
174
|
+
cacheWrite: 1000, // New context cached
|
|
175
|
+
totalTokens: 12500,
|
|
176
|
+
cost: {
|
|
177
|
+
input: 0,
|
|
178
|
+
output: 0,
|
|
179
|
+
cacheRead: 0,
|
|
180
|
+
cacheWrite: 0,
|
|
181
|
+
total: 0,
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
calculateCost(mockModel, usage);
|
|
186
|
+
|
|
187
|
+
// Cache read rate per token should be cheaper than input rate per token
|
|
188
|
+
const cacheReadPerToken = usage.cost.cacheRead / usage.cacheRead;
|
|
189
|
+
const inputPerToken = usage.cost.input / usage.input;
|
|
190
|
+
expect(cacheReadPerToken).toBeLessThan(inputPerToken);
|
|
191
|
+
|
|
192
|
+
// Total should be much cheaper than if all 12500 were input tokens
|
|
193
|
+
const allInputCost = (12500 * mockModel.cost.input) / 1000000;
|
|
194
|
+
expect(usage.cost.total).toBeLessThan(allInputCost);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe('Zero token cases', () => {
|
|
199
|
+
it('should return zero cost for zero tokens', () => {
|
|
200
|
+
const usage: Usage = {
|
|
201
|
+
input: 0,
|
|
202
|
+
output: 0,
|
|
203
|
+
cacheRead: 0,
|
|
204
|
+
cacheWrite: 0,
|
|
205
|
+
totalTokens: 0,
|
|
206
|
+
cost: {
|
|
207
|
+
input: 0,
|
|
208
|
+
output: 0,
|
|
209
|
+
cacheRead: 0,
|
|
210
|
+
cacheWrite: 0,
|
|
211
|
+
total: 0,
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
calculateCost(mockModel, usage);
|
|
216
|
+
|
|
217
|
+
expect(usage.cost.input).toBe(0);
|
|
218
|
+
expect(usage.cost.output).toBe(0);
|
|
219
|
+
expect(usage.cost.cacheRead).toBe(0);
|
|
220
|
+
expect(usage.cost.cacheWrite).toBe(0);
|
|
221
|
+
expect(usage.cost.total).toBe(0);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should handle zero cost model', () => {
|
|
225
|
+
const freeModel: Model<'openai'> = {
|
|
226
|
+
...mockModel,
|
|
227
|
+
cost: {
|
|
228
|
+
input: 0,
|
|
229
|
+
output: 0,
|
|
230
|
+
cacheRead: 0,
|
|
231
|
+
cacheWrite: 0,
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const usage: Usage = {
|
|
236
|
+
input: 10000,
|
|
237
|
+
output: 5000,
|
|
238
|
+
cacheRead: 0,
|
|
239
|
+
cacheWrite: 0,
|
|
240
|
+
totalTokens: 15000,
|
|
241
|
+
cost: {
|
|
242
|
+
input: 0,
|
|
243
|
+
output: 0,
|
|
244
|
+
cacheRead: 0,
|
|
245
|
+
cacheWrite: 0,
|
|
246
|
+
total: 0,
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
calculateCost(freeModel, usage);
|
|
251
|
+
|
|
252
|
+
expect(usage.cost.total).toBe(0);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe('High token counts', () => {
|
|
257
|
+
it('should accurately calculate cost for large token counts', () => {
|
|
258
|
+
const usage: Usage = {
|
|
259
|
+
input: 100000, // 100k tokens
|
|
260
|
+
output: 50000, // 50k tokens
|
|
261
|
+
cacheRead: 0,
|
|
262
|
+
cacheWrite: 0,
|
|
263
|
+
totalTokens: 150000,
|
|
264
|
+
cost: {
|
|
265
|
+
input: 0,
|
|
266
|
+
output: 0,
|
|
267
|
+
cacheRead: 0,
|
|
268
|
+
cacheWrite: 0,
|
|
269
|
+
total: 0,
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
calculateCost(mockModel, usage);
|
|
274
|
+
|
|
275
|
+
// Input: 100000 * 0.15 / 1M = 0.015
|
|
276
|
+
// Output: 50000 * 0.60 / 1M = 0.03
|
|
277
|
+
// Total: 0.045
|
|
278
|
+
expect(usage.cost.input).toBeCloseTo(0.015, 10);
|
|
279
|
+
expect(usage.cost.output).toBeCloseTo(0.03, 10);
|
|
280
|
+
expect(usage.cost.total).toBeCloseTo(0.045, 10);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('should handle million+ token counts', () => {
|
|
284
|
+
const usage: Usage = {
|
|
285
|
+
input: 2000000, // 2 million tokens
|
|
286
|
+
output: 1000000, // 1 million tokens
|
|
287
|
+
cacheRead: 0,
|
|
288
|
+
cacheWrite: 0,
|
|
289
|
+
totalTokens: 3000000,
|
|
290
|
+
cost: {
|
|
291
|
+
input: 0,
|
|
292
|
+
output: 0,
|
|
293
|
+
cacheRead: 0,
|
|
294
|
+
cacheWrite: 0,
|
|
295
|
+
total: 0,
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
calculateCost(mockModel, usage);
|
|
300
|
+
|
|
301
|
+
// Input: 2M * 0.15 / 1M = 0.30
|
|
302
|
+
// Output: 1M * 0.60 / 1M = 0.60
|
|
303
|
+
// Total: 0.90
|
|
304
|
+
expect(usage.cost.input).toBeCloseTo(0.30, 10);
|
|
305
|
+
expect(usage.cost.output).toBeCloseTo(0.60, 10);
|
|
306
|
+
expect(usage.cost.total).toBeCloseTo(0.90, 10);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
describe('Different model pricing', () => {
|
|
311
|
+
it('should calculate cost for expensive model', () => {
|
|
312
|
+
const expensiveModel: Model<'openai'> = {
|
|
313
|
+
...mockModel,
|
|
314
|
+
cost: {
|
|
315
|
+
input: 5.00, // $5 per million
|
|
316
|
+
output: 15.00, // $15 per million
|
|
317
|
+
cacheRead: 0.50,
|
|
318
|
+
cacheWrite: 2.50,
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const usage: Usage = {
|
|
323
|
+
input: 1000,
|
|
324
|
+
output: 1000,
|
|
325
|
+
cacheRead: 0,
|
|
326
|
+
cacheWrite: 0,
|
|
327
|
+
totalTokens: 2000,
|
|
328
|
+
cost: {
|
|
329
|
+
input: 0,
|
|
330
|
+
output: 0,
|
|
331
|
+
cacheRead: 0,
|
|
332
|
+
cacheWrite: 0,
|
|
333
|
+
total: 0,
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
calculateCost(expensiveModel, usage);
|
|
338
|
+
|
|
339
|
+
// Input: 1000 * 5 / 1M = 0.005
|
|
340
|
+
// Output: 1000 * 15 / 1M = 0.015
|
|
341
|
+
// Total: 0.02
|
|
342
|
+
expect(usage.cost.total).toBeCloseTo(0.02, 10);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('should calculate cost for cheap model', () => {
|
|
346
|
+
const cheapModel: Model<'openai'> = {
|
|
347
|
+
...mockModel,
|
|
348
|
+
cost: {
|
|
349
|
+
input: 0.01, // $0.01 per million
|
|
350
|
+
output: 0.03, // $0.03 per million
|
|
351
|
+
cacheRead: 0.001,
|
|
352
|
+
cacheWrite: 0.005,
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const usage: Usage = {
|
|
357
|
+
input: 10000,
|
|
358
|
+
output: 10000,
|
|
359
|
+
cacheRead: 0,
|
|
360
|
+
cacheWrite: 0,
|
|
361
|
+
totalTokens: 20000,
|
|
362
|
+
cost: {
|
|
363
|
+
input: 0,
|
|
364
|
+
output: 0,
|
|
365
|
+
cacheRead: 0,
|
|
366
|
+
cacheWrite: 0,
|
|
367
|
+
total: 0,
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
calculateCost(cheapModel, usage);
|
|
372
|
+
|
|
373
|
+
// Input: 10000 * 0.01 / 1M = 0.0001
|
|
374
|
+
// Output: 10000 * 0.03 / 1M = 0.0003
|
|
375
|
+
// Total: 0.0004
|
|
376
|
+
expect(usage.cost.total).toBeCloseTo(0.0004, 10);
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
describe('Floating point precision', () => {
|
|
381
|
+
it('should maintain precision for very small costs', () => {
|
|
382
|
+
const usage: Usage = {
|
|
383
|
+
input: 1, // Single token
|
|
384
|
+
output: 1,
|
|
385
|
+
cacheRead: 0,
|
|
386
|
+
cacheWrite: 0,
|
|
387
|
+
totalTokens: 2,
|
|
388
|
+
cost: {
|
|
389
|
+
input: 0,
|
|
390
|
+
output: 0,
|
|
391
|
+
cacheRead: 0,
|
|
392
|
+
cacheWrite: 0,
|
|
393
|
+
total: 0,
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
calculateCost(mockModel, usage);
|
|
398
|
+
|
|
399
|
+
// Should have meaningful non-zero values
|
|
400
|
+
expect(usage.cost.input).toBeGreaterThan(0);
|
|
401
|
+
expect(usage.cost.output).toBeGreaterThan(0);
|
|
402
|
+
expect(usage.cost.total).toBeGreaterThan(0);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('should handle fractional tokens (if they exist)', () => {
|
|
406
|
+
const usage: Usage = {
|
|
407
|
+
input: 1.5,
|
|
408
|
+
output: 2.7,
|
|
409
|
+
cacheRead: 0,
|
|
410
|
+
cacheWrite: 0,
|
|
411
|
+
totalTokens: 4.2,
|
|
412
|
+
cost: {
|
|
413
|
+
input: 0,
|
|
414
|
+
output: 0,
|
|
415
|
+
cacheRead: 0,
|
|
416
|
+
cacheWrite: 0,
|
|
417
|
+
total: 0,
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
calculateCost(mockModel, usage);
|
|
422
|
+
|
|
423
|
+
expect(usage.cost.total).toBeGreaterThan(0);
|
|
424
|
+
expect(Number.isFinite(usage.cost.total)).toBe(true);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
describe('Per-million token rate conversion', () => {
|
|
429
|
+
it('should correctly convert per-million rates to actual costs', () => {
|
|
430
|
+
const usage: Usage = {
|
|
431
|
+
input: 1000000, // Exactly 1 million tokens
|
|
432
|
+
output: 0,
|
|
433
|
+
cacheRead: 0,
|
|
434
|
+
cacheWrite: 0,
|
|
435
|
+
totalTokens: 1000000,
|
|
436
|
+
cost: {
|
|
437
|
+
input: 0,
|
|
438
|
+
output: 0,
|
|
439
|
+
cacheRead: 0,
|
|
440
|
+
cacheWrite: 0,
|
|
441
|
+
total: 0,
|
|
442
|
+
},
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
calculateCost(mockModel, usage);
|
|
446
|
+
|
|
447
|
+
// Should equal the per-million rate
|
|
448
|
+
expect(usage.cost.input).toBeCloseTo(mockModel.cost.input, 10);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it('should calculate half-million tokens correctly', () => {
|
|
452
|
+
const usage: Usage = {
|
|
453
|
+
input: 500000, // Half million tokens
|
|
454
|
+
output: 0,
|
|
455
|
+
cacheRead: 0,
|
|
456
|
+
cacheWrite: 0,
|
|
457
|
+
totalTokens: 500000,
|
|
458
|
+
cost: {
|
|
459
|
+
input: 0,
|
|
460
|
+
output: 0,
|
|
461
|
+
cacheRead: 0,
|
|
462
|
+
cacheWrite: 0,
|
|
463
|
+
total: 0,
|
|
464
|
+
},
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
calculateCost(mockModel, usage);
|
|
468
|
+
|
|
469
|
+
// Should be half the per-million rate
|
|
470
|
+
expect(usage.cost.input).toBeCloseTo(mockModel.cost.input / 2, 10);
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
describe('Cost mutation', () => {
|
|
475
|
+
it('should mutate the usage object in place', () => {
|
|
476
|
+
const usage: Usage = {
|
|
477
|
+
input: 1000,
|
|
478
|
+
output: 500,
|
|
479
|
+
cacheRead: 0,
|
|
480
|
+
cacheWrite: 0,
|
|
481
|
+
totalTokens: 1500,
|
|
482
|
+
cost: {
|
|
483
|
+
input: 0,
|
|
484
|
+
output: 0,
|
|
485
|
+
cacheRead: 0,
|
|
486
|
+
cacheWrite: 0,
|
|
487
|
+
total: 0,
|
|
488
|
+
},
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
const originalUsage = usage;
|
|
492
|
+
calculateCost(mockModel, usage);
|
|
493
|
+
|
|
494
|
+
// Should mutate the same object
|
|
495
|
+
expect(usage).toBe(originalUsage);
|
|
496
|
+
expect(usage.cost.total).toBeGreaterThan(0);
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
});
|