@trazum/core 2.2.1 → 2.4.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/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 +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- 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 +44 -0
- package/dist/pricing.d.ts.map +1 -1
- package/dist/pricing.js +88 -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 +20 -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 +91 -3
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What OpenAI billed, read from its own cost report.
|
|
3
|
+
*
|
|
4
|
+
* The second provider `reconcile` can set a receipt beside, and the same
|
|
5
|
+
* rule holds: the billed figure and the computed one are never added, and
|
|
6
|
+
* neither corrects the other. `anthropic-cost.ts` makes that argument once;
|
|
7
|
+
* this file adds only what is different about this report.
|
|
8
|
+
*
|
|
9
|
+
* ## The unit, which is *not* the trap here
|
|
10
|
+
*
|
|
11
|
+
* Anthropic's `amount` is a decimal string in cents. OpenAI's is an object,
|
|
12
|
+
* `{"value": 0.06, "currency": "usd"}`, and the schema's own example is a
|
|
13
|
+
* day's bucket at six cents: `value` is **a number in the currency's major
|
|
14
|
+
* unit**, dollars for USD. Nothing is divided here, and a test asserts that
|
|
15
|
+
* against the published example so the cents rule from the other converter
|
|
16
|
+
* cannot leak across by habit. The currency is documented as lowercase
|
|
17
|
+
* ISO-4217; it is compared case-insensitively and anything but USD is
|
|
18
|
+
* refused rather than converted, for the reason the other file gives.
|
|
19
|
+
*
|
|
20
|
+
* ## What the decomposition is for, and how little of it this report allows
|
|
21
|
+
*
|
|
22
|
+
* With `group_by[]=line_item` each row names what it charged for, in words
|
|
23
|
+
* this schema does not enumerate: *"gpt-6-astra, input_tokens"* is the
|
|
24
|
+
* example. What it does enumerate is `quantity_unit`, and that is the
|
|
25
|
+
* honest hook: a row measured in `tokens` or `1000_tokens` is a token
|
|
26
|
+
* charge, a row measured in `duration_seconds`, `images`, `characters` or
|
|
27
|
+
* `gibibyte_hours` is money no token rate ever covered. The first is the
|
|
28
|
+
* remainder's business; the second is named and set aside.
|
|
29
|
+
*
|
|
30
|
+
* A row with a line item but a `null` unit is neither, and the schema says
|
|
31
|
+
* `null` means *no single supported unit applies*. It is not guessed either
|
|
32
|
+
* way: its money is counted under its own name so the reader knows how much
|
|
33
|
+
* of the remainder is standing on a unit nobody stated.
|
|
34
|
+
*
|
|
35
|
+
* **Batch is not separable.** Nothing in this schema says whether a line
|
|
36
|
+
* item was a batch job, so unlike the Anthropic reading no batch figure can
|
|
37
|
+
* be taken out of the difference. `batchSeparable: false` says so, and the
|
|
38
|
+
* command repeats it: a batch discount, if any, is inside the remainder.
|
|
39
|
+
*/
|
|
40
|
+
const asObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
41
|
+
? value
|
|
42
|
+
: null;
|
|
43
|
+
const TOKEN_UNITS = new Set(['tokens', '1000_tokens']);
|
|
44
|
+
/** Read a cost report. Pure, and takes the answer rather than the key. */
|
|
45
|
+
export function openaiCostReport(text) {
|
|
46
|
+
const empty = {
|
|
47
|
+
usd: 0,
|
|
48
|
+
notTokensUsd: 0,
|
|
49
|
+
batchUsd: 0,
|
|
50
|
+
batchSeparable: false,
|
|
51
|
+
unknownUnitUsd: 0,
|
|
52
|
+
described: false,
|
|
53
|
+
window: null,
|
|
54
|
+
buckets: 0,
|
|
55
|
+
rows: 0,
|
|
56
|
+
otherCurrencies: [],
|
|
57
|
+
unreadableAmount: 0,
|
|
58
|
+
truncated: false,
|
|
59
|
+
unparseable: false,
|
|
60
|
+
};
|
|
61
|
+
let parsed;
|
|
62
|
+
try {
|
|
63
|
+
parsed = JSON.parse(text);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return { ...empty, unparseable: true };
|
|
67
|
+
}
|
|
68
|
+
const report = asObject(parsed);
|
|
69
|
+
if (report === null || !Array.isArray(report.data))
|
|
70
|
+
return { ...empty, unparseable: true };
|
|
71
|
+
let usd = 0;
|
|
72
|
+
let notTokensUsd = 0;
|
|
73
|
+
let unknownUnitUsd = 0;
|
|
74
|
+
let described = false;
|
|
75
|
+
let buckets = 0;
|
|
76
|
+
let rows = 0;
|
|
77
|
+
let unreadableAmount = 0;
|
|
78
|
+
let fromMs = null;
|
|
79
|
+
let toMs = null;
|
|
80
|
+
const otherCurrencies = new Set();
|
|
81
|
+
for (const entry of report.data) {
|
|
82
|
+
const bucket = asObject(entry);
|
|
83
|
+
if (bucket === null || typeof bucket.start_time !== 'number' || !Number.isFinite(bucket.start_time)) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
buckets += 1;
|
|
87
|
+
const from = bucket.start_time * 1000;
|
|
88
|
+
fromMs = fromMs === null ? from : Math.min(fromMs, from);
|
|
89
|
+
if (typeof bucket.end_time === 'number' && Number.isFinite(bucket.end_time)) {
|
|
90
|
+
const to = bucket.end_time * 1000;
|
|
91
|
+
toMs = toMs === null ? to : Math.max(toMs, to);
|
|
92
|
+
}
|
|
93
|
+
if (!Array.isArray(bucket.results))
|
|
94
|
+
continue;
|
|
95
|
+
for (const found of bucket.results) {
|
|
96
|
+
const result = asObject(found);
|
|
97
|
+
if (result === null)
|
|
98
|
+
continue;
|
|
99
|
+
rows += 1;
|
|
100
|
+
const amount = asObject(result.amount);
|
|
101
|
+
const currency = typeof amount?.currency === 'string' ? amount.currency.toLowerCase() : 'usd';
|
|
102
|
+
if (currency !== 'usd')
|
|
103
|
+
otherCurrencies.add(amount?.currency);
|
|
104
|
+
const value = amount?.value;
|
|
105
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
106
|
+
unreadableAmount += 1;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
usd += value;
|
|
110
|
+
if (typeof result.line_item === 'string') {
|
|
111
|
+
described = true;
|
|
112
|
+
const unit = result.quantity_unit;
|
|
113
|
+
if (typeof unit === 'string') {
|
|
114
|
+
if (!TOKEN_UNITS.has(unit))
|
|
115
|
+
notTokensUsd += value;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
unknownUnitUsd += value;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
usd,
|
|
125
|
+
notTokensUsd,
|
|
126
|
+
batchUsd: 0,
|
|
127
|
+
batchSeparable: false,
|
|
128
|
+
unknownUnitUsd,
|
|
129
|
+
described,
|
|
130
|
+
window: fromMs === null || toMs === null ? null : { fromMs, toMs },
|
|
131
|
+
buckets,
|
|
132
|
+
rows,
|
|
133
|
+
otherCurrencies: [...otherCurrencies],
|
|
134
|
+
unreadableAmount,
|
|
135
|
+
truncated: report.has_more === true,
|
|
136
|
+
unparseable: false,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Whether this text is an OpenAI cost report rather than Anthropic's or
|
|
141
|
+
* some other JSON. The result object names its own type; failing that, an
|
|
142
|
+
* `amount` beside a numeric `start_time` is this shape and not the other
|
|
143
|
+
* provider's, whose buckets say `starting_at`.
|
|
144
|
+
*/
|
|
145
|
+
export function looksLikeOpenaiCost(text, prefixBytes = 8192) {
|
|
146
|
+
const head = text.slice(0, prefixBytes);
|
|
147
|
+
if (head.includes('"organization.costs.result"'))
|
|
148
|
+
return true;
|
|
149
|
+
return head.includes('"amount"') && head.includes('"start_time"') && !head.includes('"starting_at"');
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=openai-cost.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-cost.js","sourceRoot":"","sources":["../src/openai-cost.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI's own usage report, read as a usage log.
|
|
3
|
+
*
|
|
4
|
+
* The seventh converter, and the second one that reads a **provider** rather
|
|
5
|
+
* than a tool sitting in front of one. `anthropic-usage.ts` argues why such a
|
|
6
|
+
* converter takes a file and never a key, and every word of that argument
|
|
7
|
+
* holds here: the Usage API needs an admin key, an admin key manages the
|
|
8
|
+
* organisation, and this project holds no provider credential of any kind.
|
|
9
|
+
* The operator runs one `curl`, in their own shell, and pipes the answer in.
|
|
10
|
+
*
|
|
11
|
+
* ## The format is derived, not guessed
|
|
12
|
+
*
|
|
13
|
+
* Every field below is from the published OpenAPI schema of
|
|
14
|
+
* `GET /v1/organization/usage/completions`: a page of `bucket` objects with
|
|
15
|
+
* `start_time` and `end_time` in **Unix seconds**, each holding `results[]`
|
|
16
|
+
* of `organization.usage.completions.result`. The schema's own example is the
|
|
17
|
+
* test fixture, field for field, because a converter tested against a shape
|
|
18
|
+
* somebody remembered is a converter that mis-reads a real bill and passes
|
|
19
|
+
* its own tests doing it.
|
|
20
|
+
*
|
|
21
|
+
* ## Why the record is the OpenAI shape and not the Anthropic one
|
|
22
|
+
*
|
|
23
|
+
* The schema says `input_tokens` is *"the aggregated number of input tokens
|
|
24
|
+
* used, including cached and cache-write tokens"*. That is the Chat
|
|
25
|
+
* Completions convention — `prompt_tokens` counts the cached half and
|
|
26
|
+
* `prompt_tokens_details.cached_tokens` says how much of it was cached — and
|
|
27
|
+
* `parseUsageLine` already subtracts through exactly that pair. So the record
|
|
28
|
+
* is written in that shape: `prompt_tokens` from `input_tokens`,
|
|
29
|
+
* `prompt_tokens_details.cached_tokens` from `input_cached_tokens`,
|
|
30
|
+
* `completion_tokens` from `output_tokens`. Writing it as Anthropic's
|
|
31
|
+
* `input_tokens` would double-charge the cached half, silently, on the
|
|
32
|
+
* largest line of the bill; the test that parses a converted record back is
|
|
33
|
+
* the one that proves the shape.
|
|
34
|
+
*
|
|
35
|
+
* The example in the schema is also an arithmetic check the converter relies
|
|
36
|
+
* on: `input_tokens` = `input_uncached_tokens` + `input_cached_tokens` +
|
|
37
|
+
* `input_cache_write_tokens`, and each of the uncached and cached totals is
|
|
38
|
+
* the sum of its text, audio and image parts. Every figure below follows from
|
|
39
|
+
* that identity and none is invented beside it.
|
|
40
|
+
*
|
|
41
|
+
* ## What it refuses to price, and counts instead
|
|
42
|
+
*
|
|
43
|
+
* **A result with no model.** `model` is `null` unless the caller passed
|
|
44
|
+
* `group_by[]=model`. Nothing on such a row says what answered, so nothing
|
|
45
|
+
* can price it: refused, counted, and the count names the missing parameter.
|
|
46
|
+
*
|
|
47
|
+
* **A batch row.** `batch` is `true` only with `group_by[]=batch`, and a batch
|
|
48
|
+
* job is billed at a discount a catalogue rate knows nothing about. Priced
|
|
49
|
+
* from that rate it would overstate the bill and look right doing it. Left
|
|
50
|
+
* out and counted. When the report never names `batch` at all, the
|
|
51
|
+
* conversion says the question was not asked, because the alternative is a
|
|
52
|
+
* reader assuming it was answered.
|
|
53
|
+
*
|
|
54
|
+
* **A service tier that is not `default`.** The schema does not enumerate
|
|
55
|
+
* the values, so this does not either: any string but `default` is left out,
|
|
56
|
+
* counted, and *named* in the conversion, so the operator sees `flex` or
|
|
57
|
+
* `priority` or whatever the report said rather than a number. Refusing
|
|
58
|
+
* by name is the honest version of not knowing the list.
|
|
59
|
+
*
|
|
60
|
+
* **Audio and image tokens.** `input_tokens` folds text, audio and image
|
|
61
|
+
* tokens together, and a text rate is not the rate for the other two. A row
|
|
62
|
+
* with any of them is reduced to its **text part** — `input_text_tokens`,
|
|
63
|
+
* `input_cached_text_tokens`, `output_text_tokens` are the schema's own split
|
|
64
|
+
* — and the audio and image tokens are counted as a named gap that no line
|
|
65
|
+
* of the output claims. Cache-write tokens carry no modality in the schema,
|
|
66
|
+
* so on such a row they are left out too, and counted, rather than guessed
|
|
67
|
+
* text. A mixed row whose report lacks the split cannot be reduced and is
|
|
68
|
+
* refused whole.
|
|
69
|
+
*
|
|
70
|
+
* **A truncated report.** `has_more: true` means one page of several, and a
|
|
71
|
+
* bill built from it is understated by whatever was not fetched.
|
|
72
|
+
*
|
|
73
|
+
* ## What deliberately does not cross
|
|
74
|
+
*
|
|
75
|
+
* `user_id` and `api_key_id` are read by nothing here: one names a person,
|
|
76
|
+
* the other a credential, and neither is a project name. `project_id` is
|
|
77
|
+
* read only through a mapping the operator writes, exactly as
|
|
78
|
+
* `--label-by-workspace` does for Anthropic, matched exactly because an
|
|
79
|
+
* opaque id has no hierarchy to take a prefix of.
|
|
80
|
+
*
|
|
81
|
+
* Unlike Anthropic's `workspace_id`, a `null` here means one thing only:
|
|
82
|
+
* the report was not grouped by project. Every OpenAI request belongs to a
|
|
83
|
+
* project and every project has an id, so there is no default named by
|
|
84
|
+
* absence and no second reading to derive.
|
|
85
|
+
*/
|
|
86
|
+
/** One project, and the label the operator gives it. Matched exactly. */
|
|
87
|
+
export interface ProjectLabel {
|
|
88
|
+
project: string;
|
|
89
|
+
label: string;
|
|
90
|
+
}
|
|
91
|
+
/** One converted record, in the shape `parseUsageLine` reads OpenAI usage. */
|
|
92
|
+
export interface OpenaiUsageRecord {
|
|
93
|
+
model: string;
|
|
94
|
+
ts: string;
|
|
95
|
+
label?: string;
|
|
96
|
+
usage: {
|
|
97
|
+
prompt_tokens: number;
|
|
98
|
+
completion_tokens: number;
|
|
99
|
+
prompt_tokens_details?: {
|
|
100
|
+
cached_tokens: number;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export interface OpenaiUsageConversion {
|
|
105
|
+
records: OpenaiUsageRecord[];
|
|
106
|
+
/** Time buckets in the report, including intervals with no usage. */
|
|
107
|
+
buckets: number;
|
|
108
|
+
/** Results that became records. */
|
|
109
|
+
rows: number;
|
|
110
|
+
/** `num_model_requests`, summed. Informative: a bucket is not a call. */
|
|
111
|
+
requests: number;
|
|
112
|
+
/** Results with `model: null`, which nothing can price. */
|
|
113
|
+
unnamedModel: number;
|
|
114
|
+
/** Results with `batch: true`, billed at a discount no catalogue rate is. */
|
|
115
|
+
batch: number;
|
|
116
|
+
/** Whether any result said whether it was batch at all. */
|
|
117
|
+
batchNamed: boolean;
|
|
118
|
+
/** Results at a service tier other than `default`, left out. */
|
|
119
|
+
nonDefaultTier: number;
|
|
120
|
+
/** The tiers those results named, so the refusal has a name. */
|
|
121
|
+
tiersRefused: string[];
|
|
122
|
+
/** Whether any result named a tier at all. */
|
|
123
|
+
tierNamed: boolean;
|
|
124
|
+
/** Results that carried audio or image tokens and were reduced to text. */
|
|
125
|
+
mixedRows: number;
|
|
126
|
+
/** Audio and image tokens, input and output, that no line of the output prices. */
|
|
127
|
+
nonTextTokens: number;
|
|
128
|
+
/** Cache-write tokens on mixed rows, left out because the schema gives them no modality. */
|
|
129
|
+
cacheWriteUnplaced: number;
|
|
130
|
+
/** Mixed rows the report gave no text split for, refused whole. */
|
|
131
|
+
unsplitRows: number;
|
|
132
|
+
/** `has_more`: this is one page and the bill from it is understated. */
|
|
133
|
+
truncated: boolean;
|
|
134
|
+
/** Records whose label came from a project rule rather than `--label`. */
|
|
135
|
+
labelledByProject: number;
|
|
136
|
+
/** Results carrying a project no rule named. They keep `--label` or none. */
|
|
137
|
+
unruledProject: number;
|
|
138
|
+
/** Rules were given and no result carried a project id. */
|
|
139
|
+
projectNotGrouped: boolean;
|
|
140
|
+
/** The input was not the JSON this endpoint returns. */
|
|
141
|
+
unparseable: number;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* @param label the project this usage belongs to, chosen by the operator.
|
|
145
|
+
* @param labelByProject one label per project id, exact match, `--label` as fallback.
|
|
146
|
+
*/
|
|
147
|
+
export declare function openaiUsageRecords(text: string, options?: {
|
|
148
|
+
label?: string;
|
|
149
|
+
labelByProject?: readonly ProjectLabel[];
|
|
150
|
+
}): OpenaiUsageConversion;
|
|
151
|
+
/**
|
|
152
|
+
* Whether this text is an OpenAI completions usage report.
|
|
153
|
+
*
|
|
154
|
+
* The result object carries its own type name, which no other document does;
|
|
155
|
+
* failing that, `num_model_requests` beside a numeric `start_time` is this
|
|
156
|
+
* endpoint's shape and not a saved response's.
|
|
157
|
+
*/
|
|
158
|
+
export declare function looksLikeOpenaiUsage(text: string, prefixBytes?: number): boolean;
|
|
159
|
+
//# sourceMappingURL=openai-usage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-usage.d.ts","sourceRoot":"","sources":["../src/openai-usage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoFG;AAEH,yEAAyE;AACzE,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,8EAA8E;AAC9E,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE;QACL,aAAa,EAAE,MAAM,CAAC;QACtB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,qBAAqB,CAAC,EAAE;YAAE,aAAa,EAAE,MAAM,CAAA;SAAE,CAAC;KACnD,CAAC;CACH;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,UAAU,EAAE,OAAO,CAAC;IACpB,gEAAgE;IAChE,cAAc,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,8CAA8C;IAC9C,SAAS,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC;IAClB,mFAAmF;IACnF,aAAa,EAAE,MAAM,CAAC;IACtB,4FAA4F;IAC5F,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mEAAmE;IACnE,WAAW,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,SAAS,EAAE,OAAO,CAAC;IACnB,0EAA0E;IAC1E,iBAAiB,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,cAAc,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,iBAAiB,EAAE,OAAO,CAAC;IAC3B,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAC;CACrB;AAoBD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,SAAS,YAAY,EAAE,CAAA;CAAO,GACzE,qBAAqB,CA0KvB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,SAAO,GAAG,OAAO,CAI9E"}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI's own usage report, read as a usage log.
|
|
3
|
+
*
|
|
4
|
+
* The seventh converter, and the second one that reads a **provider** rather
|
|
5
|
+
* than a tool sitting in front of one. `anthropic-usage.ts` argues why such a
|
|
6
|
+
* converter takes a file and never a key, and every word of that argument
|
|
7
|
+
* holds here: the Usage API needs an admin key, an admin key manages the
|
|
8
|
+
* organisation, and this project holds no provider credential of any kind.
|
|
9
|
+
* The operator runs one `curl`, in their own shell, and pipes the answer in.
|
|
10
|
+
*
|
|
11
|
+
* ## The format is derived, not guessed
|
|
12
|
+
*
|
|
13
|
+
* Every field below is from the published OpenAPI schema of
|
|
14
|
+
* `GET /v1/organization/usage/completions`: a page of `bucket` objects with
|
|
15
|
+
* `start_time` and `end_time` in **Unix seconds**, each holding `results[]`
|
|
16
|
+
* of `organization.usage.completions.result`. The schema's own example is the
|
|
17
|
+
* test fixture, field for field, because a converter tested against a shape
|
|
18
|
+
* somebody remembered is a converter that mis-reads a real bill and passes
|
|
19
|
+
* its own tests doing it.
|
|
20
|
+
*
|
|
21
|
+
* ## Why the record is the OpenAI shape and not the Anthropic one
|
|
22
|
+
*
|
|
23
|
+
* The schema says `input_tokens` is *"the aggregated number of input tokens
|
|
24
|
+
* used, including cached and cache-write tokens"*. That is the Chat
|
|
25
|
+
* Completions convention — `prompt_tokens` counts the cached half and
|
|
26
|
+
* `prompt_tokens_details.cached_tokens` says how much of it was cached — and
|
|
27
|
+
* `parseUsageLine` already subtracts through exactly that pair. So the record
|
|
28
|
+
* is written in that shape: `prompt_tokens` from `input_tokens`,
|
|
29
|
+
* `prompt_tokens_details.cached_tokens` from `input_cached_tokens`,
|
|
30
|
+
* `completion_tokens` from `output_tokens`. Writing it as Anthropic's
|
|
31
|
+
* `input_tokens` would double-charge the cached half, silently, on the
|
|
32
|
+
* largest line of the bill; the test that parses a converted record back is
|
|
33
|
+
* the one that proves the shape.
|
|
34
|
+
*
|
|
35
|
+
* The example in the schema is also an arithmetic check the converter relies
|
|
36
|
+
* on: `input_tokens` = `input_uncached_tokens` + `input_cached_tokens` +
|
|
37
|
+
* `input_cache_write_tokens`, and each of the uncached and cached totals is
|
|
38
|
+
* the sum of its text, audio and image parts. Every figure below follows from
|
|
39
|
+
* that identity and none is invented beside it.
|
|
40
|
+
*
|
|
41
|
+
* ## What it refuses to price, and counts instead
|
|
42
|
+
*
|
|
43
|
+
* **A result with no model.** `model` is `null` unless the caller passed
|
|
44
|
+
* `group_by[]=model`. Nothing on such a row says what answered, so nothing
|
|
45
|
+
* can price it: refused, counted, and the count names the missing parameter.
|
|
46
|
+
*
|
|
47
|
+
* **A batch row.** `batch` is `true` only with `group_by[]=batch`, and a batch
|
|
48
|
+
* job is billed at a discount a catalogue rate knows nothing about. Priced
|
|
49
|
+
* from that rate it would overstate the bill and look right doing it. Left
|
|
50
|
+
* out and counted. When the report never names `batch` at all, the
|
|
51
|
+
* conversion says the question was not asked, because the alternative is a
|
|
52
|
+
* reader assuming it was answered.
|
|
53
|
+
*
|
|
54
|
+
* **A service tier that is not `default`.** The schema does not enumerate
|
|
55
|
+
* the values, so this does not either: any string but `default` is left out,
|
|
56
|
+
* counted, and *named* in the conversion, so the operator sees `flex` or
|
|
57
|
+
* `priority` or whatever the report said rather than a number. Refusing
|
|
58
|
+
* by name is the honest version of not knowing the list.
|
|
59
|
+
*
|
|
60
|
+
* **Audio and image tokens.** `input_tokens` folds text, audio and image
|
|
61
|
+
* tokens together, and a text rate is not the rate for the other two. A row
|
|
62
|
+
* with any of them is reduced to its **text part** — `input_text_tokens`,
|
|
63
|
+
* `input_cached_text_tokens`, `output_text_tokens` are the schema's own split
|
|
64
|
+
* — and the audio and image tokens are counted as a named gap that no line
|
|
65
|
+
* of the output claims. Cache-write tokens carry no modality in the schema,
|
|
66
|
+
* so on such a row they are left out too, and counted, rather than guessed
|
|
67
|
+
* text. A mixed row whose report lacks the split cannot be reduced and is
|
|
68
|
+
* refused whole.
|
|
69
|
+
*
|
|
70
|
+
* **A truncated report.** `has_more: true` means one page of several, and a
|
|
71
|
+
* bill built from it is understated by whatever was not fetched.
|
|
72
|
+
*
|
|
73
|
+
* ## What deliberately does not cross
|
|
74
|
+
*
|
|
75
|
+
* `user_id` and `api_key_id` are read by nothing here: one names a person,
|
|
76
|
+
* the other a credential, and neither is a project name. `project_id` is
|
|
77
|
+
* read only through a mapping the operator writes, exactly as
|
|
78
|
+
* `--label-by-workspace` does for Anthropic, matched exactly because an
|
|
79
|
+
* opaque id has no hierarchy to take a prefix of.
|
|
80
|
+
*
|
|
81
|
+
* Unlike Anthropic's `workspace_id`, a `null` here means one thing only:
|
|
82
|
+
* the report was not grouped by project. Every OpenAI request belongs to a
|
|
83
|
+
* project and every project has an id, so there is no default named by
|
|
84
|
+
* absence and no second reading to derive.
|
|
85
|
+
*/
|
|
86
|
+
const count = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : 0;
|
|
87
|
+
const asObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
88
|
+
? value
|
|
89
|
+
: null;
|
|
90
|
+
/** The modality fields that a text rate is not the rate for. */
|
|
91
|
+
const NON_TEXT = [
|
|
92
|
+
'input_audio_tokens',
|
|
93
|
+
'input_cached_audio_tokens',
|
|
94
|
+
'output_audio_tokens',
|
|
95
|
+
'input_image_tokens',
|
|
96
|
+
'input_cached_image_tokens',
|
|
97
|
+
'output_image_tokens',
|
|
98
|
+
];
|
|
99
|
+
/**
|
|
100
|
+
* @param label the project this usage belongs to, chosen by the operator.
|
|
101
|
+
* @param labelByProject one label per project id, exact match, `--label` as fallback.
|
|
102
|
+
*/
|
|
103
|
+
export function openaiUsageRecords(text, options = {}) {
|
|
104
|
+
const empty = {
|
|
105
|
+
records: [],
|
|
106
|
+
buckets: 0,
|
|
107
|
+
rows: 0,
|
|
108
|
+
requests: 0,
|
|
109
|
+
unnamedModel: 0,
|
|
110
|
+
batch: 0,
|
|
111
|
+
batchNamed: false,
|
|
112
|
+
nonDefaultTier: 0,
|
|
113
|
+
tiersRefused: [],
|
|
114
|
+
tierNamed: false,
|
|
115
|
+
mixedRows: 0,
|
|
116
|
+
nonTextTokens: 0,
|
|
117
|
+
cacheWriteUnplaced: 0,
|
|
118
|
+
unsplitRows: 0,
|
|
119
|
+
truncated: false,
|
|
120
|
+
labelledByProject: 0,
|
|
121
|
+
unruledProject: 0,
|
|
122
|
+
projectNotGrouped: false,
|
|
123
|
+
unparseable: 0,
|
|
124
|
+
};
|
|
125
|
+
let parsed;
|
|
126
|
+
try {
|
|
127
|
+
parsed = JSON.parse(text);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return { ...empty, unparseable: 1 };
|
|
131
|
+
}
|
|
132
|
+
const report = asObject(parsed);
|
|
133
|
+
if (report === null || !Array.isArray(report.data))
|
|
134
|
+
return { ...empty, unparseable: 1 };
|
|
135
|
+
const rules = options.labelByProject;
|
|
136
|
+
const records = [];
|
|
137
|
+
const tiersRefused = new Set();
|
|
138
|
+
let buckets = 0;
|
|
139
|
+
let rows = 0;
|
|
140
|
+
let requests = 0;
|
|
141
|
+
let unnamedModel = 0;
|
|
142
|
+
let batch = 0;
|
|
143
|
+
let batchNamed = false;
|
|
144
|
+
let nonDefaultTier = 0;
|
|
145
|
+
let tierNamed = false;
|
|
146
|
+
let mixedRows = 0;
|
|
147
|
+
let nonTextTokens = 0;
|
|
148
|
+
let cacheWriteUnplaced = 0;
|
|
149
|
+
let unsplitRows = 0;
|
|
150
|
+
let labelledByProject = 0;
|
|
151
|
+
let unruledProject = 0;
|
|
152
|
+
let anyProject = false;
|
|
153
|
+
for (const entry of report.data) {
|
|
154
|
+
const bucket = asObject(entry);
|
|
155
|
+
if (bucket === null || typeof bucket.start_time !== 'number' || !Number.isFinite(bucket.start_time)) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
buckets += 1;
|
|
159
|
+
/* Unix seconds, per the schema, and the bucket's start is the only
|
|
160
|
+
instant a summed interval has. */
|
|
161
|
+
const ts = new Date(bucket.start_time * 1000).toISOString();
|
|
162
|
+
if (!Array.isArray(bucket.results))
|
|
163
|
+
continue;
|
|
164
|
+
for (const found of bucket.results) {
|
|
165
|
+
const result = asObject(found);
|
|
166
|
+
if (result === null)
|
|
167
|
+
continue;
|
|
168
|
+
requests += count(result.num_model_requests);
|
|
169
|
+
if (typeof result.batch === 'boolean') {
|
|
170
|
+
batchNamed = true;
|
|
171
|
+
if (result.batch) {
|
|
172
|
+
batch += 1;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (typeof result.service_tier === 'string') {
|
|
177
|
+
tierNamed = true;
|
|
178
|
+
if (result.service_tier !== 'default') {
|
|
179
|
+
nonDefaultTier += 1;
|
|
180
|
+
tiersRefused.add(result.service_tier);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (typeof result.model !== 'string' || result.model === '') {
|
|
185
|
+
unnamedModel += 1;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
let label = options.label;
|
|
189
|
+
if (rules !== undefined) {
|
|
190
|
+
const project = typeof result.project_id === 'string' ? result.project_id : null;
|
|
191
|
+
if (project !== null) {
|
|
192
|
+
anyProject = true;
|
|
193
|
+
const named = rules.find((rule) => rule.project === project);
|
|
194
|
+
if (named !== undefined) {
|
|
195
|
+
label = named.label;
|
|
196
|
+
labelledByProject += 1;
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
unruledProject += 1;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/*
|
|
204
|
+
The whole row is priced only when every token on it is text. Otherwise
|
|
205
|
+
the schema's own split is used and what is not text is counted where
|
|
206
|
+
the reader can see it, never folded into a text rate.
|
|
207
|
+
*/
|
|
208
|
+
const nonText = NON_TEXT.reduce((sum, field) => sum + count(result[field]), 0);
|
|
209
|
+
let prompt;
|
|
210
|
+
let cached;
|
|
211
|
+
let completion;
|
|
212
|
+
if (nonText === 0) {
|
|
213
|
+
prompt = count(result.input_tokens);
|
|
214
|
+
cached = count(result.input_cached_tokens);
|
|
215
|
+
completion = count(result.output_tokens);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
const split = typeof result.input_text_tokens === 'number'
|
|
219
|
+
&& typeof result.input_cached_text_tokens === 'number'
|
|
220
|
+
&& typeof result.output_text_tokens === 'number';
|
|
221
|
+
if (!split) {
|
|
222
|
+
unsplitRows += 1;
|
|
223
|
+
nonTextTokens += nonText;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
mixedRows += 1;
|
|
227
|
+
nonTextTokens += nonText;
|
|
228
|
+
cacheWriteUnplaced += count(result.input_cache_write_tokens);
|
|
229
|
+
cached = count(result.input_cached_text_tokens);
|
|
230
|
+
prompt = count(result.input_text_tokens) + cached;
|
|
231
|
+
completion = count(result.output_text_tokens);
|
|
232
|
+
}
|
|
233
|
+
records.push({
|
|
234
|
+
model: result.model,
|
|
235
|
+
ts,
|
|
236
|
+
...(label === undefined ? {} : { label }),
|
|
237
|
+
usage: {
|
|
238
|
+
prompt_tokens: prompt,
|
|
239
|
+
completion_tokens: completion,
|
|
240
|
+
...(cached > 0 ? { prompt_tokens_details: { cached_tokens: cached } } : {}),
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
rows += 1;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
records,
|
|
248
|
+
buckets,
|
|
249
|
+
rows,
|
|
250
|
+
requests,
|
|
251
|
+
unnamedModel,
|
|
252
|
+
batch,
|
|
253
|
+
batchNamed,
|
|
254
|
+
nonDefaultTier,
|
|
255
|
+
tiersRefused: [...tiersRefused],
|
|
256
|
+
tierNamed,
|
|
257
|
+
mixedRows,
|
|
258
|
+
nonTextTokens,
|
|
259
|
+
cacheWriteUnplaced,
|
|
260
|
+
unsplitRows,
|
|
261
|
+
truncated: report.has_more === true,
|
|
262
|
+
labelledByProject,
|
|
263
|
+
unruledProject,
|
|
264
|
+
projectNotGrouped: rules !== undefined && !anyProject,
|
|
265
|
+
unparseable: 0,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Whether this text is an OpenAI completions usage report.
|
|
270
|
+
*
|
|
271
|
+
* The result object carries its own type name, which no other document does;
|
|
272
|
+
* failing that, `num_model_requests` beside a numeric `start_time` is this
|
|
273
|
+
* endpoint's shape and not a saved response's.
|
|
274
|
+
*/
|
|
275
|
+
export function looksLikeOpenaiUsage(text, prefixBytes = 8192) {
|
|
276
|
+
const head = text.slice(0, prefixBytes);
|
|
277
|
+
if (head.includes('"organization.usage.completions.result"'))
|
|
278
|
+
return true;
|
|
279
|
+
return head.includes('"num_model_requests"') && head.includes('"start_time"');
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=openai-usage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-usage.js","sourceRoot":"","sources":["../src/openai-usage.ts"],"names":[],"mappings":"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"}
|