@trazum/core 2.3.0 → 2.4.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/dist/anthropic-cost.d.ts +173 -0
- package/dist/anthropic-cost.d.ts.map +1 -0
- package/dist/anthropic-cost.js +222 -0
- package/dist/anthropic-cost.js.map +1 -0
- package/dist/anthropic-usage.d.ts +192 -0
- package/dist/anthropic-usage.d.ts.map +1 -0
- package/dist/anthropic-usage.js +260 -0
- package/dist/anthropic-usage.js.map +1 -0
- package/dist/claude-code.d.ts +29 -1
- package/dist/claude-code.d.ts.map +1 -1
- package/dist/claude-code.js +33 -1
- package/dist/claude-code.js.map +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/openai-cost.d.ts +62 -0
- package/dist/openai-cost.d.ts.map +1 -0
- package/dist/openai-cost.js +151 -0
- package/dist/openai-cost.js.map +1 -0
- package/dist/openai-usage.d.ts +159 -0
- package/dist/openai-usage.d.ts.map +1 -0
- package/dist/openai-usage.js +281 -0
- package/dist/openai-usage.js.map +1 -0
- package/dist/openrouter-activity.d.ts +103 -0
- package/dist/openrouter-activity.d.ts.map +1 -0
- package/dist/openrouter-activity.js +169 -0
- package/dist/openrouter-activity.js.map +1 -0
- package/dist/pricing.d.ts.map +1 -1
- package/dist/pricing.js +25 -3
- package/dist/pricing.js.map +1 -1
- package/package.json +2 -2
- package/src/anthropic-cost.ts +303 -0
- package/src/anthropic-usage.ts +363 -0
- package/src/claude-code.ts +62 -2
- package/src/index.ts +19 -1
- package/src/openai-cost.ts +170 -0
- package/src/openai-usage.ts +353 -0
- package/src/openrouter-activity.ts +221 -0
- package/src/pricing.ts +27 -3
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenRouter's activity report, read as a usage log.
|
|
3
|
+
*
|
|
4
|
+
* The eighth converter, and the third that reads a provider rather than a
|
|
5
|
+
* tool in front of one. OpenRouter is the one provider Trazum already has a
|
|
6
|
+
* price list for by construction: `openrouter.ts` turns its public model
|
|
7
|
+
* catalogue into a pricing overlay, keyed by the same slugs this report
|
|
8
|
+
* carries. So a row here is priceable the moment the overlay is loaded, for
|
|
9
|
+
* hundreds of models the bundled table never typed.
|
|
10
|
+
*
|
|
11
|
+
* ## Derived from the published schema
|
|
12
|
+
*
|
|
13
|
+
* `GET /api/v1/activity` needs a **management key** and answers the last
|
|
14
|
+
* thirty completed UTC days, one row per model per endpoint per day:
|
|
15
|
+
* `date` (`YYYY-MM-DD`), `model` (slug), `model_permaslug` (slug with
|
|
16
|
+
* version), `endpoint_id`, `provider_name`, `usage` (cost in USD),
|
|
17
|
+
* `byok_usage_inference` (BYOK cost in USD), `requests`, `prompt_tokens`,
|
|
18
|
+
* `completion_tokens`, `reasoning_tokens`, and `workspace_id` when the
|
|
19
|
+
* request said `group_by=workspace`. The schema's own example is the
|
|
20
|
+
* fixture.
|
|
21
|
+
*
|
|
22
|
+
* It takes the answer and never the key, for the reason `anthropic-usage.ts`
|
|
23
|
+
* gives: a management key manages the account, and this project holds no
|
|
24
|
+
* provider credential.
|
|
25
|
+
*
|
|
26
|
+
* ## Two figures that are never added
|
|
27
|
+
*
|
|
28
|
+
* `usage` is what OpenRouter charged, in dollars, and it is carried out of
|
|
29
|
+
* this conversion **beside** Trazum's catalogue-priced total, never merged
|
|
30
|
+
* into it — the rule `docs/commands.md` states for LiteLLM's `spend`. They
|
|
31
|
+
* are two measurements of the same rows; a reader is entitled to both and to
|
|
32
|
+
* the gap. `byok_usage_inference` is what an upstream provider charged on the
|
|
33
|
+
* operator's own key through OpenRouter, and is carried separately for the
|
|
34
|
+
* same reason.
|
|
35
|
+
*
|
|
36
|
+
* ## What the record says, and what it declines to say
|
|
37
|
+
*
|
|
38
|
+
* The record is `prompt_tokens` and `completion_tokens` under the model's
|
|
39
|
+
* slug, at the day's start. `model` rather than `model_permaslug`, because
|
|
40
|
+
* the slug is what the pricing overlay is keyed by and a versioned slug
|
|
41
|
+
* would price nothing until somebody mapped it.
|
|
42
|
+
*
|
|
43
|
+
* `reasoning_tokens` are counted and **not added** to the completion count.
|
|
44
|
+
* The schema does not say whether `completion_tokens` already includes
|
|
45
|
+
* them, and adding them if it does would charge reasoning twice on the
|
|
46
|
+
* models where it is the largest line. They are reported so the operator
|
|
47
|
+
* can see the count and settle the question against their own invoice.
|
|
48
|
+
*
|
|
49
|
+
* `endpoint_id` and `provider_name` are read by nothing that reaches the
|
|
50
|
+
* output: a row is priced by model, and which provider served it is what
|
|
51
|
+
* `usage` already reflects. `workspace_id` is read only through a mapping
|
|
52
|
+
* the operator writes, exact match, and unlike Anthropic's a `null` here has
|
|
53
|
+
* one meaning only — the request did not group by workspace.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/** One workspace, and the label the operator gives it. Exact match. */
|
|
57
|
+
export interface OpenrouterWorkspaceLabel {
|
|
58
|
+
workspace: string;
|
|
59
|
+
label: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface OpenrouterActivityRecord {
|
|
63
|
+
model: string;
|
|
64
|
+
ts: string;
|
|
65
|
+
label?: string;
|
|
66
|
+
usage: { prompt_tokens: number; completion_tokens: number };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface OpenrouterActivityConversion {
|
|
70
|
+
records: OpenrouterActivityRecord[];
|
|
71
|
+
/** Rows that became records. */
|
|
72
|
+
rows: number;
|
|
73
|
+
/** `requests`, summed. A row is a day's aggregate, not a call. */
|
|
74
|
+
requests: number;
|
|
75
|
+
/** What OpenRouter charged, summed, in dollars. Never merged into Trazum's total. */
|
|
76
|
+
reportedUsageUsd: number;
|
|
77
|
+
/** What upstream providers charged on the operator's own keys, summed. */
|
|
78
|
+
byokUsd: number;
|
|
79
|
+
/** `reasoning_tokens`, summed, and deliberately not added to any record. */
|
|
80
|
+
reasoningTokens: number;
|
|
81
|
+
/** Rows with no model slug, which nothing can price. */
|
|
82
|
+
unnamedModel: number;
|
|
83
|
+
/** Rows whose date was not a `YYYY-MM-DD` day. */
|
|
84
|
+
undatedRows: number;
|
|
85
|
+
/** Distinct days seen, so a reader knows how much of the thirty this is. */
|
|
86
|
+
days: number;
|
|
87
|
+
labelledByWorkspace: number;
|
|
88
|
+
unruledWorkspace: number;
|
|
89
|
+
/** Rules were given and no row carried a workspace id. */
|
|
90
|
+
workspaceNotGrouped: boolean;
|
|
91
|
+
/** The input was not the JSON this endpoint returns. */
|
|
92
|
+
unparseable: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const count = (value: unknown): number =>
|
|
96
|
+
typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : 0;
|
|
97
|
+
|
|
98
|
+
const asObject = (value: unknown): Record<string, unknown> | null =>
|
|
99
|
+
typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
100
|
+
? (value as Record<string, unknown>)
|
|
101
|
+
: null;
|
|
102
|
+
|
|
103
|
+
const DAY = /^\d{4}-\d{2}-\d{2}$/;
|
|
104
|
+
|
|
105
|
+
export function openrouterActivityRecords(
|
|
106
|
+
text: string,
|
|
107
|
+
options: { label?: string; labelByWorkspace?: readonly OpenrouterWorkspaceLabel[] } = {},
|
|
108
|
+
): OpenrouterActivityConversion {
|
|
109
|
+
const empty: OpenrouterActivityConversion = {
|
|
110
|
+
records: [],
|
|
111
|
+
rows: 0,
|
|
112
|
+
requests: 0,
|
|
113
|
+
reportedUsageUsd: 0,
|
|
114
|
+
byokUsd: 0,
|
|
115
|
+
reasoningTokens: 0,
|
|
116
|
+
unnamedModel: 0,
|
|
117
|
+
undatedRows: 0,
|
|
118
|
+
days: 0,
|
|
119
|
+
labelledByWorkspace: 0,
|
|
120
|
+
unruledWorkspace: 0,
|
|
121
|
+
workspaceNotGrouped: false,
|
|
122
|
+
unparseable: 0,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
let parsed: unknown;
|
|
126
|
+
try {
|
|
127
|
+
parsed = JSON.parse(text);
|
|
128
|
+
} catch {
|
|
129
|
+
return { ...empty, unparseable: 1 };
|
|
130
|
+
}
|
|
131
|
+
const report = asObject(parsed);
|
|
132
|
+
if (report === null || !Array.isArray(report.data)) return { ...empty, unparseable: 1 };
|
|
133
|
+
|
|
134
|
+
const rules = options.labelByWorkspace;
|
|
135
|
+
const records: OpenrouterActivityRecord[] = [];
|
|
136
|
+
const days = new Set<string>();
|
|
137
|
+
let rows = 0;
|
|
138
|
+
let requests = 0;
|
|
139
|
+
let reportedUsageUsd = 0;
|
|
140
|
+
let byokUsd = 0;
|
|
141
|
+
let reasoningTokens = 0;
|
|
142
|
+
let unnamedModel = 0;
|
|
143
|
+
let undatedRows = 0;
|
|
144
|
+
let labelledByWorkspace = 0;
|
|
145
|
+
let unruledWorkspace = 0;
|
|
146
|
+
let anyWorkspace = false;
|
|
147
|
+
|
|
148
|
+
for (const found of report.data) {
|
|
149
|
+
const row = asObject(found);
|
|
150
|
+
if (row === null) continue;
|
|
151
|
+
|
|
152
|
+
/* The two billed figures are summed over every row, refused or not: a
|
|
153
|
+
refused row was still charged for, and a total that left it out would
|
|
154
|
+
be understated in the direction nobody questions. */
|
|
155
|
+
reportedUsageUsd += count(row.usage);
|
|
156
|
+
byokUsd += count(row.byok_usage_inference);
|
|
157
|
+
requests += count(row.requests);
|
|
158
|
+
reasoningTokens += count(row.reasoning_tokens);
|
|
159
|
+
|
|
160
|
+
if (typeof row.date !== 'string' || !DAY.test(row.date)) {
|
|
161
|
+
undatedRows += 1;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (typeof row.model !== 'string' || row.model === '') {
|
|
165
|
+
unnamedModel += 1;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
days.add(row.date);
|
|
169
|
+
|
|
170
|
+
let label = options.label;
|
|
171
|
+
if (rules !== undefined) {
|
|
172
|
+
const workspace = typeof row.workspace_id === 'string' ? row.workspace_id : null;
|
|
173
|
+
if (workspace !== null) {
|
|
174
|
+
anyWorkspace = true;
|
|
175
|
+
const named = rules.find((rule) => rule.workspace === workspace);
|
|
176
|
+
if (named !== undefined) {
|
|
177
|
+
label = named.label;
|
|
178
|
+
labelledByWorkspace += 1;
|
|
179
|
+
} else {
|
|
180
|
+
unruledWorkspace += 1;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
records.push({
|
|
186
|
+
model: row.model,
|
|
187
|
+
ts: `${row.date}T00:00:00.000Z`,
|
|
188
|
+
...(label === undefined ? {} : { label }),
|
|
189
|
+
usage: {
|
|
190
|
+
prompt_tokens: count(row.prompt_tokens),
|
|
191
|
+
completion_tokens: count(row.completion_tokens),
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
rows += 1;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
records,
|
|
199
|
+
rows,
|
|
200
|
+
requests,
|
|
201
|
+
reportedUsageUsd,
|
|
202
|
+
byokUsd,
|
|
203
|
+
reasoningTokens,
|
|
204
|
+
unnamedModel,
|
|
205
|
+
undatedRows,
|
|
206
|
+
days: days.size,
|
|
207
|
+
labelledByWorkspace,
|
|
208
|
+
unruledWorkspace,
|
|
209
|
+
workspaceNotGrouped: rules !== undefined && !anyWorkspace,
|
|
210
|
+
unparseable: 0,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Whether this text is an OpenRouter activity report. `model_permaslug`
|
|
216
|
+
* belongs to no other document, and `byok_usage_inference` likewise.
|
|
217
|
+
*/
|
|
218
|
+
export function looksLikeOpenrouterActivity(text: string, prefixBytes = 8192): boolean {
|
|
219
|
+
const head = text.slice(0, prefixBytes);
|
|
220
|
+
return head.includes('"model_permaslug"') || head.includes('"byok_usage_inference"');
|
|
221
|
+
}
|
package/src/pricing.ts
CHANGED
|
@@ -47,10 +47,30 @@ export const PROVIDER_REVIEWED: Readonly<Record<string, string>> = Object.freeze
|
|
|
47
47
|
openai: '2026-08-31',
|
|
48
48
|
/* ai.google.dev/gemini-api/docs/pricing, read 2026-08-28. */
|
|
49
49
|
google: '2026-08-28',
|
|
50
|
-
|
|
50
|
+
/*
|
|
51
|
+
* platform.kimi.ai/docs/pricing/chat (platform.moonshot.ai 301s there),
|
|
52
|
+
* read 2026-09-12. The page prices kimi-k3, kimi-k2.6, kimi-k2.7-code and
|
|
53
|
+
* its highspeed variant; `kimi-k2`, the one id this table carries, is no
|
|
54
|
+
* longer on it. Its price is kept because calls in somebody's log really
|
|
55
|
+
* happened at it, and it is not marked retired because that is recorded
|
|
56
|
+
* from the provider's own refusal to a request, which needs a key this
|
|
57
|
+
* repository does not hold. The newer models are not added: the page states
|
|
58
|
+
* no context window for any of them, and a context window this table did
|
|
59
|
+
* not read is a number it would be inventing.
|
|
60
|
+
*/
|
|
61
|
+
moonshot: '2026-09-12',
|
|
51
62
|
/* api-docs.deepseek.com/quick_start/pricing, read 2026-08-28. */
|
|
52
63
|
deepseek: '2026-08-28',
|
|
53
|
-
|
|
64
|
+
/*
|
|
65
|
+
* docs.x.ai/docs/models, read 2026-09-12. The page prices grok-4.6, 4.5,
|
|
66
|
+
* 4.3 and the 4.20 family, each at two rates split at 200k prompt tokens;
|
|
67
|
+
* `grok-4`, the one id this table carries, is no longer on it. Kept and not
|
|
68
|
+
* marked retired, for the reasons given for kimi-k2 above. The newer models
|
|
69
|
+
* are not added: this table has no way to express a price that changes at
|
|
70
|
+
* 200k prompt tokens, and pricing a 300k-token call at the under-200k rate
|
|
71
|
+
* would understate it by half while looking exact.
|
|
72
|
+
*/
|
|
73
|
+
xai: '2026-09-12',
|
|
54
74
|
/*
|
|
55
75
|
* `mistral.ai/pricing`, read 2026-08-28 — by a human, because the page
|
|
56
76
|
* renders its table in the browser and serves a fetch only an FAQ example
|
|
@@ -379,7 +399,9 @@ export const MODELS: ModelPricing[] = [
|
|
|
379
399
|
multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
|
|
380
400
|
capability: 'mid',
|
|
381
401
|
tier: 'sonnet',
|
|
382
|
-
notes:
|
|
402
|
+
notes:
|
|
403
|
+
'No batch API: the batch advisory stays quiet rather than offering a discount you cannot buy.'
|
|
404
|
+
+ ' Not on the provider\'s pricing page as of 2026-09-12, which lists kimi-k3 and kimi-k2.6 instead; the price is the last one published for it.',
|
|
383
405
|
},
|
|
384
406
|
|
|
385
407
|
// ------------------------------------------------------------------------
|
|
@@ -475,6 +497,8 @@ export const MODELS: ModelPricing[] = [
|
|
|
475
497
|
multipliers: { cacheRead: 0.25, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
|
|
476
498
|
capability: 'large',
|
|
477
499
|
tier: 'opus',
|
|
500
|
+
notes:
|
|
501
|
+
'Not on the provider\'s model page as of 2026-09-12, which lists grok-4.6 and later instead; the price is the last one published for it.',
|
|
478
502
|
},
|
|
479
503
|
|
|
480
504
|
// ------------------------------------------------------------------------
|