@trazum/core 2.4.2 → 2.5.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/dist/advisories.d.ts.map +1 -1
- package/dist/advisories.js +17 -10
- package/dist/advisories.js.map +1 -1
- package/dist/answer.d.ts.map +1 -1
- package/dist/answer.js +2 -1
- package/dist/answer.js.map +1 -1
- package/dist/anthropic-usage.d.ts +6 -0
- package/dist/anthropic-usage.d.ts.map +1 -1
- package/dist/anthropic-usage.js +1 -0
- package/dist/anthropic-usage.js.map +1 -1
- package/dist/billing.d.ts +20 -0
- package/dist/billing.d.ts.map +1 -0
- package/dist/billing.js +32 -0
- package/dist/billing.js.map +1 -0
- package/dist/budget.d.ts.map +1 -1
- package/dist/budget.js +18 -8
- package/dist/budget.js.map +1 -1
- package/dist/connector.d.ts +20 -1
- package/dist/connector.d.ts.map +1 -1
- package/dist/connector.js +42 -11
- package/dist/connector.js.map +1 -1
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +4 -2
- package/dist/conversation.js.map +1 -1
- package/dist/gateway.d.ts.map +1 -1
- package/dist/gateway.js +5 -3
- package/dist/gateway.js.map +1 -1
- package/dist/guard.d.ts.map +1 -1
- package/dist/guard.js +7 -5
- package/dist/guard.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/input-shape.d.ts.map +1 -1
- package/dist/input-shape.js +4 -2
- package/dist/input-shape.js.map +1 -1
- package/dist/ladder.d.ts.map +1 -1
- package/dist/ladder.js +2 -1
- package/dist/ladder.js.map +1 -1
- package/dist/levers.d.ts.map +1 -1
- package/dist/levers.js +5 -2
- package/dist/levers.js.map +1 -1
- package/dist/openai-usage.d.ts +18 -0
- package/dist/openai-usage.d.ts.map +1 -1
- package/dist/openai-usage.js +26 -1
- package/dist/openai-usage.js.map +1 -1
- package/dist/openrouter-activity.d.ts +6 -0
- package/dist/openrouter-activity.d.ts.map +1 -1
- package/dist/openrouter-activity.js +7 -0
- package/dist/openrouter-activity.js.map +1 -1
- package/dist/output-shape.d.ts.map +1 -1
- package/dist/output-shape.js +2 -1
- package/dist/output-shape.js.map +1 -1
- package/dist/pricing.d.ts +36 -4
- package/dist/pricing.d.ts.map +1 -1
- package/dist/pricing.js +804 -91
- package/dist/pricing.js.map +1 -1
- package/dist/receipt.d.ts +17 -0
- package/dist/receipt.d.ts.map +1 -1
- package/dist/receipt.js +10 -0
- package/dist/receipt.js.map +1 -1
- package/dist/repeats.d.ts.map +1 -1
- package/dist/repeats.js +4 -2
- package/dist/repeats.js.map +1 -1
- package/dist/reprice.d.ts +18 -2
- package/dist/reprice.d.ts.map +1 -1
- package/dist/reprice.js +34 -12
- package/dist/reprice.js.map +1 -1
- package/dist/rollup.d.ts.map +1 -1
- package/dist/rollup.js +3 -0
- package/dist/rollup.js.map +1 -1
- package/dist/savings.d.ts.map +1 -1
- package/dist/savings.js +10 -3
- package/dist/savings.js.map +1 -1
- package/dist/session-cost.d.ts.map +1 -1
- package/dist/session-cost.js +5 -2
- package/dist/session-cost.js.map +1 -1
- package/dist/session-ledger.d.ts.map +1 -1
- package/dist/session-ledger.js +12 -7
- package/dist/session-ledger.js.map +1 -1
- package/dist/truncation-retry.d.ts.map +1 -1
- package/dist/truncation-retry.js +4 -2
- package/dist/truncation-retry.js.map +1 -1
- package/dist/ttl-fit.d.ts +8 -0
- package/dist/ttl-fit.d.ts.map +1 -1
- package/dist/ttl-fit.js +25 -14
- package/dist/ttl-fit.js.map +1 -1
- package/dist/types.d.ts +51 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/usage.d.ts +36 -1
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +91 -10
- package/dist/usage.js.map +1 -1
- package/package.json +1 -1
- package/src/advisories.ts +18 -10
- package/src/answer.ts +6 -1
- package/src/anthropic-usage.ts +7 -0
- package/src/billing.ts +37 -0
- package/src/budget.ts +18 -8
- package/src/connector.ts +58 -10
- package/src/conversation.ts +4 -2
- package/src/gateway.ts +5 -3
- package/src/guard.ts +7 -5
- package/src/index.ts +1 -1
- package/src/input-shape.ts +4 -2
- package/src/ladder.ts +2 -1
- package/src/levers.ts +5 -2
- package/src/openai-usage.ts +44 -2
- package/src/openrouter-activity.ts +13 -0
- package/src/output-shape.ts +2 -1
- package/src/pricing.ts +854 -96
- package/src/receipt.ts +23 -1
- package/src/repeats.ts +4 -2
- package/src/reprice.ts +57 -16
- package/src/rollup.ts +3 -0
- package/src/savings.ts +12 -5
- package/src/session-cost.ts +5 -2
- package/src/session-ledger.ts +18 -9
- package/src/truncation-retry.ts +4 -2
- package/src/ttl-fit.ts +39 -15
- package/src/types.ts +46 -0
- package/src/usage.ts +129 -11
package/src/gateway.ts
CHANGED
|
@@ -216,7 +216,8 @@ function priceCall(
|
|
|
216
216
|
on: Date,
|
|
217
217
|
): number | null {
|
|
218
218
|
if (model === undefined || call.inputTokens === null) return null;
|
|
219
|
-
|
|
219
|
+
// The call's own prompt decides a model priced by prompt length.
|
|
220
|
+
const rates = effectivePricing(model, on, { inputTokens: call.inputTokens });
|
|
220
221
|
const output = call.maxOutputTokens ?? 0;
|
|
221
222
|
return (call.inputTokens / 1_000_000) * rates.inputPerMTok + (output / 1_000_000) * rates.outputPerMTok;
|
|
222
223
|
}
|
|
@@ -236,11 +237,12 @@ function alternativesFor(
|
|
|
236
237
|
const mine = priceCall(model, call, on);
|
|
237
238
|
if (mine === null) return [];
|
|
238
239
|
const out: GatewayAlternative[] = [];
|
|
239
|
-
const
|
|
240
|
+
const size = call.inputTokens === null ? {} : { inputTokens: call.inputTokens };
|
|
241
|
+
const here = effectivePricing(model, on, size);
|
|
240
242
|
|
|
241
243
|
for (const candidate of catalogue.byId.values()) {
|
|
242
244
|
if (candidate.id === model.id || candidate.provider !== model.provider) continue;
|
|
243
|
-
const there = effectivePricing(candidate, on);
|
|
245
|
+
const there = effectivePricing(candidate, on, size);
|
|
244
246
|
if (there.inputPerMTok >= here.inputPerMTok) continue;
|
|
245
247
|
if (call.inputTokens !== null && candidate.contextWindow < call.inputTokens) continue;
|
|
246
248
|
const routed = priceCall(candidate, call, on);
|
package/src/guard.ts
CHANGED
|
@@ -91,18 +91,20 @@ function cheaperThan(
|
|
|
91
91
|
inputTokens: number,
|
|
92
92
|
on: Date,
|
|
93
93
|
): ModelPricing[] {
|
|
94
|
-
|
|
94
|
+
// At this call's prompt, so a model priced by prompt length is ranked at its real band.
|
|
95
|
+
const size = { inputTokens };
|
|
96
|
+
const here = effectivePricing(model, on, size);
|
|
95
97
|
return [...catalogue.byId.values()]
|
|
96
98
|
.filter((candidate) => {
|
|
97
99
|
if (candidate.id === model.id) return false;
|
|
98
100
|
if (candidate.provider !== model.provider) return false;
|
|
99
|
-
const there = effectivePricing(candidate, on);
|
|
101
|
+
const there = effectivePricing(candidate, on, size);
|
|
100
102
|
if (there.inputPerMTok >= here.inputPerMTok) return false;
|
|
101
103
|
// A model the prompt does not fit in is not a cheaper way to make this
|
|
102
104
|
// call; it is a way not to make it.
|
|
103
105
|
return candidate.contextWindow >= inputTokens;
|
|
104
106
|
})
|
|
105
|
-
.sort((a, b) => effectivePricing(b, on).inputPerMTok - effectivePricing(a, on).inputPerMTok);
|
|
107
|
+
.sort((a, b) => effectivePricing(b, on, size).inputPerMTok - effectivePricing(a, on, size).inputPerMTok);
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
export interface GuardRequest extends AnswerRequest {
|
|
@@ -133,9 +135,9 @@ export function guardSpend(
|
|
|
133
135
|
if (model !== undefined && cost.call !== null) {
|
|
134
136
|
const inputTokens = cost.call.inputTokens;
|
|
135
137
|
const outputTokens = cost.call.outputTokens;
|
|
136
|
-
const here = effectivePricing(model, on);
|
|
138
|
+
const here = effectivePricing(model, on, { inputTokens });
|
|
137
139
|
const priceOf = (candidate: ModelPricing): number => {
|
|
138
|
-
const rates = effectivePricing(candidate, on);
|
|
140
|
+
const rates = effectivePricing(candidate, on, { inputTokens });
|
|
139
141
|
return (inputTokens / 1_000_000) * rates.inputPerMTok + (outputTokens / 1_000_000) * rates.outputPerMTok;
|
|
140
142
|
};
|
|
141
143
|
const mine = (inputTokens / 1_000_000) * here.inputPerMTok + (outputTokens / 1_000_000) * here.outputPerMTok;
|
package/src/index.ts
CHANGED
|
@@ -435,7 +435,7 @@ export {
|
|
|
435
435
|
multipliersFor,
|
|
436
436
|
isOffered,
|
|
437
437
|
} from './pricing.js';
|
|
438
|
-
export type { PricingCatalogue } from './pricing.js';
|
|
438
|
+
export type { PricingCatalogue, PromptSize } from './pricing.js';
|
|
439
439
|
export type { CachingMode, Capability, CostMultipliers } from './types.js';
|
|
440
440
|
|
|
441
441
|
// Local price corrections, so a price change needs no library upgrade.
|
package/src/input-shape.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { effectivePricing, multipliersFor } from './pricing.js';
|
|
2
|
+
import { billedAt, promptSizeOf } from './billing.js';
|
|
2
3
|
import { UNLABELLED } from './usage.js';
|
|
3
4
|
import type { PricingCatalogue } from './pricing.js';
|
|
4
5
|
import type { UsageRecord } from './usage.js';
|
|
@@ -190,8 +191,9 @@ export function createInputShapeTracker(options: InputShapeOptions): InputShapeT
|
|
|
190
191
|
slices.set(key, slice);
|
|
191
192
|
}
|
|
192
193
|
|
|
193
|
-
const
|
|
194
|
-
const
|
|
194
|
+
const at = billedAt(record, on);
|
|
195
|
+
const { inputPerMTok } = effectivePricing(model, at, promptSizeOf(record));
|
|
196
|
+
const rates = multipliersFor(model, at);
|
|
195
197
|
const per = (count: number, rate: number): number => (count / 1_000_000) * rate;
|
|
196
198
|
|
|
197
199
|
slice.calls += 1;
|
package/src/ladder.ts
CHANGED
|
@@ -151,7 +151,8 @@ export function ladderArithmetic(
|
|
|
151
151
|
if (id === undefined) return null;
|
|
152
152
|
const model = catalogue.byId.get(id);
|
|
153
153
|
if (model === undefined) return null;
|
|
154
|
-
|
|
154
|
+
// The shape's own prompt decides a model priced by prompt length.
|
|
155
|
+
const rates = effectivePricing(model, on, { inputTokens: shape.inputTokens });
|
|
155
156
|
return (
|
|
156
157
|
(shape.inputTokens / 1_000_000) * rates.inputPerMTok +
|
|
157
158
|
(shape.outputTokens / 1_000_000) * rates.outputPerMTok
|
package/src/levers.ts
CHANGED
|
@@ -150,8 +150,11 @@ function repriceAt(
|
|
|
150
150
|
model: ModelPricing,
|
|
151
151
|
on: Date,
|
|
152
152
|
): { inputUsd: number; outputUsd: number; cacheUsd: number; totalUsd: number } {
|
|
153
|
-
|
|
154
|
-
const
|
|
153
|
+
// The breakdown's largest call bounds every prompt in it: see `PromptSize`.
|
|
154
|
+
const { inputPerMTok, outputPerMTok } = effectivePricing(model, on, {
|
|
155
|
+
inputTokensAtMost: breakdown.maxCallInputTokens,
|
|
156
|
+
});
|
|
157
|
+
const rates = multipliersFor(model, on);
|
|
155
158
|
const per = (tokens: number, rate: number): number => (tokens / 1_000_000) * rate;
|
|
156
159
|
|
|
157
160
|
const inputUsd = per(breakdown.inputTokens, inputPerMTok);
|
package/src/openai-usage.ts
CHANGED
|
@@ -95,10 +95,21 @@ export interface OpenaiUsageRecord {
|
|
|
95
95
|
model: string;
|
|
96
96
|
ts: string;
|
|
97
97
|
label?: string;
|
|
98
|
+
/**
|
|
99
|
+
* `num_model_requests` when the bucket sums more than one, `null` when the
|
|
100
|
+
* row does not carry a usable count, and absent when it is exactly one
|
|
101
|
+
* request, which is then a call like any other. See `UsageRecord.requests`.
|
|
102
|
+
*/
|
|
103
|
+
requests?: number | null;
|
|
98
104
|
usage: {
|
|
99
105
|
prompt_tokens: number;
|
|
100
106
|
completion_tokens: number;
|
|
101
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Both inside `prompt_tokens`, as OpenAI counts them, and both taken out of
|
|
109
|
+
* it by `parseUsageLine`. `cache_write_tokens` is the 30-minute write
|
|
110
|
+
* GPT-5.6 and later bill at 1.25x input.
|
|
111
|
+
*/
|
|
112
|
+
prompt_tokens_details?: { cached_tokens: number; cache_write_tokens?: number };
|
|
102
113
|
};
|
|
103
114
|
}
|
|
104
115
|
|
|
@@ -128,6 +139,12 @@ export interface OpenaiUsageConversion {
|
|
|
128
139
|
nonTextTokens: number;
|
|
129
140
|
/** Cache-write tokens on mixed rows, left out because the schema gives them no modality. */
|
|
130
141
|
cacheWriteUnplaced: number;
|
|
142
|
+
/**
|
|
143
|
+
* Tokens written to the 12-hour cache. No price for that write is published
|
|
144
|
+
* anywhere this table can read, so they stay in the prompt total and are
|
|
145
|
+
* priced as plain input, a floor, and named rather than guessed at.
|
|
146
|
+
*/
|
|
147
|
+
cacheWrite12hTokens: number;
|
|
131
148
|
/** Mixed rows the report gave no text split for, refused whole. */
|
|
132
149
|
unsplitRows: number;
|
|
133
150
|
/** `has_more`: this is one page and the bill from it is understated. */
|
|
@@ -182,6 +199,7 @@ export function openaiUsageRecords(
|
|
|
182
199
|
mixedRows: 0,
|
|
183
200
|
nonTextTokens: 0,
|
|
184
201
|
cacheWriteUnplaced: 0,
|
|
202
|
+
cacheWrite12hTokens: 0,
|
|
185
203
|
unsplitRows: 0,
|
|
186
204
|
truncated: false,
|
|
187
205
|
labelledByProject: 0,
|
|
@@ -213,6 +231,7 @@ export function openaiUsageRecords(
|
|
|
213
231
|
let mixedRows = 0;
|
|
214
232
|
let nonTextTokens = 0;
|
|
215
233
|
let cacheWriteUnplaced = 0;
|
|
234
|
+
let cacheWrite12hTokens = 0;
|
|
216
235
|
let unsplitRows = 0;
|
|
217
236
|
let labelledByProject = 0;
|
|
218
237
|
let unruledProject = 0;
|
|
@@ -279,10 +298,18 @@ export function openaiUsageRecords(
|
|
|
279
298
|
const nonText = NON_TEXT.reduce((sum, field) => sum + count(result[field]), 0);
|
|
280
299
|
let prompt: number;
|
|
281
300
|
let cached: number;
|
|
301
|
+
let written = 0;
|
|
282
302
|
let completion: number;
|
|
283
303
|
if (nonText === 0) {
|
|
284
304
|
prompt = count(result.input_tokens);
|
|
285
305
|
cached = count(result.input_cached_tokens);
|
|
306
|
+
/*
|
|
307
|
+
`input_tokens` is uncached + cached + both writes, by the schema's own
|
|
308
|
+
example. The 30-minute write has a price and goes where the parser
|
|
309
|
+
prices it; the 12-hour write has none here and stays in the total.
|
|
310
|
+
*/
|
|
311
|
+
written = count(result.input_cache_write_tokens);
|
|
312
|
+
cacheWrite12hTokens += count(result.input_cache_write_12h_tokens);
|
|
286
313
|
completion = count(result.output_tokens);
|
|
287
314
|
} else {
|
|
288
315
|
const split =
|
|
@@ -306,10 +333,18 @@ export function openaiUsageRecords(
|
|
|
306
333
|
model: result.model,
|
|
307
334
|
ts,
|
|
308
335
|
...(label === undefined ? {} : { label }),
|
|
336
|
+
...requestsField(result.num_model_requests),
|
|
309
337
|
usage: {
|
|
310
338
|
prompt_tokens: prompt,
|
|
311
339
|
completion_tokens: completion,
|
|
312
|
-
...(cached > 0
|
|
340
|
+
...(cached > 0 || written > 0
|
|
341
|
+
? {
|
|
342
|
+
prompt_tokens_details: {
|
|
343
|
+
cached_tokens: cached,
|
|
344
|
+
...(written > 0 ? { cache_write_tokens: written } : {}),
|
|
345
|
+
},
|
|
346
|
+
}
|
|
347
|
+
: {}),
|
|
313
348
|
},
|
|
314
349
|
});
|
|
315
350
|
rows += 1;
|
|
@@ -330,6 +365,7 @@ export function openaiUsageRecords(
|
|
|
330
365
|
mixedRows,
|
|
331
366
|
nonTextTokens,
|
|
332
367
|
cacheWriteUnplaced,
|
|
368
|
+
cacheWrite12hTokens,
|
|
333
369
|
unsplitRows,
|
|
334
370
|
truncated: report.has_more === true,
|
|
335
371
|
labelledByProject,
|
|
@@ -351,3 +387,9 @@ export function looksLikeOpenaiUsage(text: string, prefixBytes = 8192): boolean
|
|
|
351
387
|
if (head.includes('"organization.usage.completions.result"')) return true;
|
|
352
388
|
return head.includes('"num_model_requests"') && head.includes('"start_time"');
|
|
353
389
|
}
|
|
390
|
+
|
|
391
|
+
/** A bucket's request count as `UsageRecord.requests` reads it: one is a call. */
|
|
392
|
+
function requestsField(value: unknown): { requests?: number | null } {
|
|
393
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < 1) return { requests: null };
|
|
394
|
+
return value === 1 ? {} : { requests: value };
|
|
395
|
+
}
|
|
@@ -63,6 +63,12 @@ export interface OpenrouterActivityRecord {
|
|
|
63
63
|
model: string;
|
|
64
64
|
ts: string;
|
|
65
65
|
label?: string;
|
|
66
|
+
/**
|
|
67
|
+
* The day's `requests` when it is more than one, `null` when the row does
|
|
68
|
+
* not carry a usable count, absent when it is exactly one request. See
|
|
69
|
+
* `UsageRecord.requests`.
|
|
70
|
+
*/
|
|
71
|
+
requests?: number | null;
|
|
66
72
|
usage: { prompt_tokens: number; completion_tokens: number };
|
|
67
73
|
}
|
|
68
74
|
|
|
@@ -186,6 +192,7 @@ export function openrouterActivityRecords(
|
|
|
186
192
|
model: row.model,
|
|
187
193
|
ts: `${row.date}T00:00:00.000Z`,
|
|
188
194
|
...(label === undefined ? {} : { label }),
|
|
195
|
+
...requestsField(row.requests),
|
|
189
196
|
usage: {
|
|
190
197
|
prompt_tokens: count(row.prompt_tokens),
|
|
191
198
|
completion_tokens: count(row.completion_tokens),
|
|
@@ -219,3 +226,9 @@ export function looksLikeOpenrouterActivity(text: string, prefixBytes = 8192): b
|
|
|
219
226
|
const head = text.slice(0, prefixBytes);
|
|
220
227
|
return head.includes('"model_permaslug"') || head.includes('"byok_usage_inference"');
|
|
221
228
|
}
|
|
229
|
+
|
|
230
|
+
/** A day's request count as `UsageRecord.requests` reads it: one is a call. */
|
|
231
|
+
function requestsField(value: unknown): { requests?: number | null } {
|
|
232
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < 1) return { requests: null };
|
|
233
|
+
return value === 1 ? {} : { requests: value };
|
|
234
|
+
}
|
package/src/output-shape.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { effectivePricing } from './pricing.js';
|
|
2
|
+
import { billedAt, promptSizeOf } from './billing.js';
|
|
2
3
|
import { UNLABELLED } from './usage.js';
|
|
3
4
|
import type { PricingCatalogue } from './pricing.js';
|
|
4
5
|
import type { UsageRecord } from './usage.js';
|
|
@@ -172,7 +173,7 @@ export function createOutputShapeTracker(options: OutputShapeOptions): OutputSha
|
|
|
172
173
|
slices.set(key, slice);
|
|
173
174
|
}
|
|
174
175
|
|
|
175
|
-
const { outputPerMTok } = effectivePricing(model, on);
|
|
176
|
+
const { outputPerMTok } = effectivePricing(model, billedAt(record, on), promptSizeOf(record));
|
|
176
177
|
slice.calls += 1;
|
|
177
178
|
slice.outputTokens += record.outputTokens;
|
|
178
179
|
slice.outputUsd += (record.outputTokens / 1_000_000) * outputPerMTok;
|