@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.
Files changed (40) hide show
  1. package/dist/anthropic-cost.d.ts +173 -0
  2. package/dist/anthropic-cost.d.ts.map +1 -0
  3. package/dist/anthropic-cost.js +222 -0
  4. package/dist/anthropic-cost.js.map +1 -0
  5. package/dist/anthropic-usage.d.ts +192 -0
  6. package/dist/anthropic-usage.d.ts.map +1 -0
  7. package/dist/anthropic-usage.js +260 -0
  8. package/dist/anthropic-usage.js.map +1 -0
  9. package/dist/claude-code.d.ts +29 -1
  10. package/dist/claude-code.d.ts.map +1 -1
  11. package/dist/claude-code.js +33 -1
  12. package/dist/claude-code.js.map +1 -1
  13. package/dist/index.d.ts +11 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +5 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/openai-cost.d.ts +62 -0
  18. package/dist/openai-cost.d.ts.map +1 -0
  19. package/dist/openai-cost.js +151 -0
  20. package/dist/openai-cost.js.map +1 -0
  21. package/dist/openai-usage.d.ts +159 -0
  22. package/dist/openai-usage.d.ts.map +1 -0
  23. package/dist/openai-usage.js +281 -0
  24. package/dist/openai-usage.js.map +1 -0
  25. package/dist/openrouter-activity.d.ts +103 -0
  26. package/dist/openrouter-activity.d.ts.map +1 -0
  27. package/dist/openrouter-activity.js +169 -0
  28. package/dist/openrouter-activity.js.map +1 -0
  29. package/dist/pricing.d.ts.map +1 -1
  30. package/dist/pricing.js +25 -3
  31. package/dist/pricing.js.map +1 -1
  32. package/package.json +2 -2
  33. package/src/anthropic-cost.ts +303 -0
  34. package/src/anthropic-usage.ts +363 -0
  35. package/src/claude-code.ts +62 -2
  36. package/src/index.ts +19 -1
  37. package/src/openai-cost.ts +170 -0
  38. package/src/openai-usage.ts +353 -0
  39. package/src/openrouter-activity.ts +221 -0
  40. package/src/pricing.ts +27 -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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"}