@trazum/core 2.4.3 → 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 +35 -3
- package/dist/pricing.d.ts.map +1 -1
- package/dist/pricing.js +569 -54
- 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 +24 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/usage.d.ts +31 -0
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +79 -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 +614 -59
- 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 +18 -0
- package/src/usage.ts +112 -10
package/src/usage.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { effectivePricing, multipliersFor } from './pricing.js';
|
|
1
|
+
import { cacheTtlOf, effectivePricing, multipliersFor } from './pricing.js';
|
|
2
|
+
import { billedAt, promptSizeOf } from './billing.js';
|
|
2
3
|
import { createConversationTracker } from './conversation.js';
|
|
3
4
|
import { createOutputShapeTracker } from './output-shape.js';
|
|
4
5
|
import { createInputShapeTracker } from './input-shape.js';
|
|
@@ -159,6 +160,22 @@ export interface UsageRecord {
|
|
|
159
160
|
* must not congratulate a log that never measured.
|
|
160
161
|
*/
|
|
161
162
|
truncated: boolean | null;
|
|
163
|
+
/**
|
|
164
|
+
* How many requests this record sums, when it sums more than one.
|
|
165
|
+
*
|
|
166
|
+
* Absent on a record that is one call, which is every record a per-call log
|
|
167
|
+
* holds. A number above one, or `null` for "a sum, count not given", is a
|
|
168
|
+
* row out of a usage report: an Anthropic or OpenAI bucket, an OpenRouter
|
|
169
|
+
* day. It matters to a model priced by prompt length, because a request is
|
|
170
|
+
* priced on its own prompt and a row of many requests does not say any one
|
|
171
|
+
* of them. Such a row is priced at the dearer rate unless its whole prompt
|
|
172
|
+
* total is at or under the line, and the report says so.
|
|
173
|
+
*
|
|
174
|
+
* Read from `requests` when it is a positive integer or `null`; anything
|
|
175
|
+
* else is not this field, because a log that already uses the name for
|
|
176
|
+
* something of its own should not lose its records to it.
|
|
177
|
+
*/
|
|
178
|
+
requests?: number | null;
|
|
162
179
|
}
|
|
163
180
|
|
|
164
181
|
/** What a set of calls cost, split by where the money went. */
|
|
@@ -239,6 +256,21 @@ export interface UsageBreakdown {
|
|
|
239
256
|
truncatedOutputUsd: number;
|
|
240
257
|
/** Calls that recorded a stop reason at all, truncated or not. */
|
|
241
258
|
stopReasonCalls: number;
|
|
259
|
+
/**
|
|
260
|
+
* Records billed at the dearer of two rates because which one applied could
|
|
261
|
+
* not be decided, and what they cost each way.
|
|
262
|
+
*
|
|
263
|
+
* A model priced by prompt length bills each request on its own prompt, and
|
|
264
|
+
* a row that sums requests does not say any one prompt's length. Those rows
|
|
265
|
+
* are billed at the dearer rate, so `totalUsd` is a ceiling on them and never
|
|
266
|
+
* a figure that flatters; `tierCeilingUsd` is what they contribute to it and
|
|
267
|
+
* `tierFloorUsd` what the same tokens come to at the cheaper rate. The true
|
|
268
|
+
* bill for them is between the two, and a report that printed only the
|
|
269
|
+
* ceiling would be right and useless.
|
|
270
|
+
*/
|
|
271
|
+
tierCeilingCalls: number;
|
|
272
|
+
tierCeilingUsd: number;
|
|
273
|
+
tierFloorUsd: number;
|
|
242
274
|
}
|
|
243
275
|
|
|
244
276
|
/**
|
|
@@ -597,6 +629,9 @@ const EMPTY = (): UsageBreakdown => ({
|
|
|
597
629
|
truncatedCalls: 0,
|
|
598
630
|
truncatedOutputUsd: 0,
|
|
599
631
|
stopReasonCalls: 0,
|
|
632
|
+
tierCeilingCalls: 0,
|
|
633
|
+
tierCeilingUsd: 0,
|
|
634
|
+
tierFloorUsd: 0,
|
|
600
635
|
});
|
|
601
636
|
|
|
602
637
|
/**
|
|
@@ -713,12 +748,24 @@ export function parseUsageLine(line: string): UsageRecord | null {
|
|
|
713
748
|
* in `prompt_tokens`**, while Anthropic reports them separately and does not.
|
|
714
749
|
* Subtracting in one case and not the other is the difference between a correct
|
|
715
750
|
* bill and one that charges the cached half twice.
|
|
751
|
+
*
|
|
752
|
+
* **And the Responses API spells it `input_tokens_details`**, under an
|
|
753
|
+
* `input_tokens` that likewise includes the cached tokens. Only the Chat
|
|
754
|
+
* Completions spelling was read, so a Responses log, OpenAI's default API,
|
|
755
|
+
* billed every cached token at the full input rate: a call that read 90,000
|
|
756
|
+
* of its 100,000 tokens from the cache came out four times its price, and the
|
|
757
|
+
* cache verdict said caching was never used.
|
|
758
|
+
*
|
|
759
|
+
* GPT-5.6 and later also report `cache_write_tokens` in the same object,
|
|
760
|
+
* inside the same total, and bill them at 1.25x input. They are subtracted
|
|
761
|
+
* with the cached tokens and land in the write bucket, whose TTL the model
|
|
762
|
+
* states (`cacheTtl`): one kind of entry, so nothing about it is assumed.
|
|
716
763
|
*/
|
|
764
|
+
const detailsRaw = usage.prompt_tokens_details ?? usage.input_tokens_details;
|
|
717
765
|
const details =
|
|
718
|
-
typeof
|
|
719
|
-
? (usage.prompt_tokens_details as Record<string, unknown>)
|
|
720
|
-
: null;
|
|
766
|
+
typeof detailsRaw === 'object' && detailsRaw !== null ? (detailsRaw as Record<string, unknown>) : null;
|
|
721
767
|
const openAiCached = details ? readCount(details.cached_tokens) : ({ kind: 'absent' } as Count);
|
|
768
|
+
const openAiWrite = details ? readCount(details.cache_write_tokens) : ({ kind: 'absent' } as Count);
|
|
722
769
|
|
|
723
770
|
/**
|
|
724
771
|
* Anthropic splits cache writes by time-to-live, and the two cost different
|
|
@@ -769,6 +816,7 @@ export function parseUsageLine(line: string): UsageRecord | null {
|
|
|
769
816
|
cacheRead: readCount(usage.cache_read_input_tokens, usage.cacheReadTokens),
|
|
770
817
|
cacheWrite: readCount(usage.cache_creation_input_tokens, usage.cacheWriteTokens),
|
|
771
818
|
openAiCached,
|
|
819
|
+
openAiWrite,
|
|
772
820
|
geminiCached,
|
|
773
821
|
write5m,
|
|
774
822
|
write1h,
|
|
@@ -791,6 +839,8 @@ export function parseUsageLine(line: string): UsageRecord | null {
|
|
|
791
839
|
// Both providers that fold cached tokens into the prompt count, one
|
|
792
840
|
// subtraction. They cannot coexist on a record: the shapes share no field.
|
|
793
841
|
const cached = valueOf(counts.openAiCached!) + valueOf(counts.geminiCached!);
|
|
842
|
+
// Inside the OpenAI total like the cached tokens, and taken out of it the same way.
|
|
843
|
+
const openAiWritten = valueOf(counts.openAiWrite!);
|
|
794
844
|
const flatWrite = valueOf(counts.cacheWrite!);
|
|
795
845
|
const split5m = valueOf(counts.write5m!);
|
|
796
846
|
const split1h = valueOf(counts.write1h!);
|
|
@@ -798,16 +848,17 @@ export function parseUsageLine(line: string): UsageRecord | null {
|
|
|
798
848
|
|
|
799
849
|
return {
|
|
800
850
|
model,
|
|
801
|
-
inputTokens: Math.max(0, valueOf(counts.input!) - cached),
|
|
851
|
+
inputTokens: Math.max(0, valueOf(counts.input!) - cached - openAiWritten),
|
|
802
852
|
cacheReadTokens: counts.cacheRead!.kind === 'ok' ? counts.cacheRead!.value : cached,
|
|
803
853
|
/**
|
|
804
854
|
* The split when the log carries it, the flat number otherwise — and
|
|
805
855
|
* `writeTtlKnown` says which, so the report can admit that a rate was assumed
|
|
806
856
|
* rather than quietly choosing the cheaper one.
|
|
807
857
|
*/
|
|
808
|
-
cacheWrite5mTokens: hasSplit ? split5m : flatWrite,
|
|
858
|
+
cacheWrite5mTokens: (hasSplit ? split5m : flatWrite) + openAiWritten,
|
|
809
859
|
cacheWrite1hTokens: hasSplit ? split1h : 0,
|
|
810
|
-
|
|
860
|
+
// `cache_write_tokens` names no TTL either; see the note in `profileUsage`.
|
|
861
|
+
writeTtlKnown: hasSplit || (flatWrite === 0 && openAiWritten === 0),
|
|
811
862
|
outputTokens: valueOf(counts.output!),
|
|
812
863
|
/**
|
|
813
864
|
* Trimmed and internally normalised: any whitespace run becomes one space.
|
|
@@ -846,9 +897,22 @@ export function parseUsageLine(line: string): UsageRecord | null {
|
|
|
846
897
|
if (typeof reason !== 'string') return null;
|
|
847
898
|
return reason === 'max_tokens' || reason === 'length' || reason === 'MAX_TOKENS';
|
|
848
899
|
})(),
|
|
900
|
+
...requestsOf(record.requests),
|
|
849
901
|
};
|
|
850
902
|
}
|
|
851
903
|
|
|
904
|
+
/**
|
|
905
|
+
* `requests`, when it is this field: a positive integer or `null`.
|
|
906
|
+
*
|
|
907
|
+
* One is the same as absent, so it is left absent and a per-call record stays
|
|
908
|
+
* the shape it always was.
|
|
909
|
+
*/
|
|
910
|
+
function requestsOf(value: unknown): { requests?: number | null } {
|
|
911
|
+
if (value === null) return { requests: null };
|
|
912
|
+
if (typeof value === 'number' && Number.isInteger(value) && value > 1) return { requests: value };
|
|
913
|
+
return {};
|
|
914
|
+
}
|
|
915
|
+
|
|
852
916
|
/**
|
|
853
917
|
* One slice's outcome tally, in the same shape as the whole log's.
|
|
854
918
|
*
|
|
@@ -954,11 +1018,30 @@ function add(into: UsageBreakdown, record: UsageRecord, catalogue: PricingCatalo
|
|
|
954
1018
|
* August log read in October lost its August promotion. `on` is the date for
|
|
955
1019
|
* a record that carries no clock, which is the only thing it can mean here.
|
|
956
1020
|
*/
|
|
957
|
-
const at =
|
|
958
|
-
|
|
1021
|
+
const at = billedAt(record, on);
|
|
1022
|
+
/**
|
|
1023
|
+
* **Each request on its own prompt.** A model priced by prompt length counts
|
|
1024
|
+
* every input token a request carried, cache reads and writes included, and
|
|
1025
|
+
* a record that is one call says exactly that. A record that sums requests
|
|
1026
|
+
* gives only a bound: no request in it was larger than the whole row.
|
|
1027
|
+
*/
|
|
1028
|
+
const priced = effectivePricing(model, at, promptSizeOf(record));
|
|
1029
|
+
const { inputPerMTok, outputPerMTok } = priced;
|
|
959
1030
|
// Dated, like the rate above: a repriced cache read is part of the reprice.
|
|
960
1031
|
const rates = multipliersFor(model, at);
|
|
961
1032
|
const per = (tokens: number, rate: number): number => (tokens / 1_000_000) * rate;
|
|
1033
|
+
const recordUsd = (input: number, output: number): number =>
|
|
1034
|
+
per(record.inputTokens, input) +
|
|
1035
|
+
per(record.cacheReadTokens, input * rates.cacheRead) +
|
|
1036
|
+
per(record.cacheWrite5mTokens, input * rates.cacheWrite5m) +
|
|
1037
|
+
per(record.cacheWrite1hTokens, input * rates.cacheWrite1h) +
|
|
1038
|
+
per(record.outputTokens, output);
|
|
1039
|
+
const floor = priced.tier?.decided === false ? priced.tier.floor : undefined;
|
|
1040
|
+
if (floor !== undefined) {
|
|
1041
|
+
into.tierCeilingCalls += 1;
|
|
1042
|
+
into.tierCeilingUsd += recordUsd(inputPerMTok, outputPerMTok);
|
|
1043
|
+
into.tierFloorUsd += recordUsd(floor.inputPerMTok, floor.outputPerMTok);
|
|
1044
|
+
}
|
|
962
1045
|
|
|
963
1046
|
into.inputUsd += per(record.inputTokens, inputPerMTok);
|
|
964
1047
|
into.cacheReadUsd += per(record.cacheReadTokens, inputPerMTok * rates.cacheRead);
|
|
@@ -1105,11 +1188,30 @@ export function profileUsage(text: string, options: UsageProfileOptions): UsageP
|
|
|
1105
1188
|
const line = lines[i]!.trim();
|
|
1106
1189
|
if (line === '') continue;
|
|
1107
1190
|
|
|
1108
|
-
|
|
1191
|
+
let record = parseUsageLine(line);
|
|
1109
1192
|
if (!record) {
|
|
1110
1193
|
skippedLines.push(i + 1);
|
|
1111
1194
|
continue;
|
|
1112
1195
|
}
|
|
1196
|
+
/**
|
|
1197
|
+
* A write whose TTL the line did not state is an assumption only where
|
|
1198
|
+
* there was a choice. `cache_write_tokens` carries no TTL: on Kimi K3,
|
|
1199
|
+
* which sells a 5-minute and a 1-hour entry, the cheaper is assumed and
|
|
1200
|
+
* the report says so; on OpenAI's GPT-5.6 and later, which sell one, and
|
|
1201
|
+
* on its earlier models, whose writes cost what input costs whatever they
|
|
1202
|
+
* last, there is nothing to assume and nothing is said.
|
|
1203
|
+
*/
|
|
1204
|
+
if (!record.writeTtlKnown) {
|
|
1205
|
+
const priced = catalogue.byId.get(record.model);
|
|
1206
|
+
// One kind of entry, or two at one price: an assumption that cannot move the bill is not one.
|
|
1207
|
+
if (
|
|
1208
|
+
priced !== undefined
|
|
1209
|
+
&& (cacheTtlOf(priced).longerMs === null
|
|
1210
|
+
|| multipliersFor(priced).cacheWrite1h === multipliersFor(priced).cacheWrite5m)
|
|
1211
|
+
) {
|
|
1212
|
+
record = { ...record, writeTtlKnown: true };
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1113
1215
|
|
|
1114
1216
|
// The drill-down: after the skip accounting, so a corrupt line is reported
|
|
1115
1217
|
// whatever it might have been labelled.
|