@trazum/core 1.8.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.
Files changed (223) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +163 -0
  3. package/dist/advisories.d.ts +23 -0
  4. package/dist/advisories.d.ts.map +1 -0
  5. package/dist/advisories.js +376 -0
  6. package/dist/advisories.js.map +1 -0
  7. package/dist/aws-sigv4.d.ts +88 -0
  8. package/dist/aws-sigv4.d.ts.map +1 -0
  9. package/dist/aws-sigv4.js +117 -0
  10. package/dist/aws-sigv4.js.map +1 -0
  11. package/dist/baseline.d.ts +171 -0
  12. package/dist/baseline.d.ts.map +1 -0
  13. package/dist/baseline.js +273 -0
  14. package/dist/baseline.js.map +1 -0
  15. package/dist/cache.d.ts +26 -0
  16. package/dist/cache.d.ts.map +1 -0
  17. package/dist/cache.js +28 -0
  18. package/dist/cache.js.map +1 -0
  19. package/dist/changes.d.ts +29 -0
  20. package/dist/changes.d.ts.map +1 -0
  21. package/dist/changes.js +142 -0
  22. package/dist/changes.js.map +1 -0
  23. package/dist/compare.d.ts +65 -0
  24. package/dist/compare.d.ts.map +1 -0
  25. package/dist/compare.js +58 -0
  26. package/dist/compare.js.map +1 -0
  27. package/dist/config-schema.d.ts +118 -0
  28. package/dist/config-schema.d.ts.map +1 -0
  29. package/dist/config-schema.js +315 -0
  30. package/dist/config-schema.js.map +1 -0
  31. package/dist/config.d.ts +47 -0
  32. package/dist/config.d.ts.map +1 -0
  33. package/dist/config.js +112 -0
  34. package/dist/config.js.map +1 -0
  35. package/dist/detect.d.ts +70 -0
  36. package/dist/detect.d.ts.map +1 -0
  37. package/dist/detect.js +228 -0
  38. package/dist/detect.js.map +1 -0
  39. package/dist/evaluate.d.ts +98 -0
  40. package/dist/evaluate.d.ts.map +1 -0
  41. package/dist/evaluate.js +110 -0
  42. package/dist/evaluate.js.map +1 -0
  43. package/dist/extract.d.ts +81 -0
  44. package/dist/extract.d.ts.map +1 -0
  45. package/dist/extract.js +280 -0
  46. package/dist/extract.js.map +1 -0
  47. package/dist/gcp-auth.d.ts +58 -0
  48. package/dist/gcp-auth.d.ts.map +1 -0
  49. package/dist/gcp-auth.js +113 -0
  50. package/dist/gcp-auth.js.map +1 -0
  51. package/dist/glob.d.ts +49 -0
  52. package/dist/glob.d.ts.map +1 -0
  53. package/dist/glob.js +154 -0
  54. package/dist/glob.js.map +1 -0
  55. package/dist/host.d.ts +30 -0
  56. package/dist/host.d.ts.map +1 -0
  57. package/dist/host.js +69 -0
  58. package/dist/host.js.map +1 -0
  59. package/dist/i18n/en.d.ts +4 -0
  60. package/dist/i18n/en.d.ts.map +1 -0
  61. package/dist/i18n/en.js +168 -0
  62. package/dist/i18n/en.js.map +1 -0
  63. package/dist/i18n/es.d.ts +4 -0
  64. package/dist/i18n/es.d.ts.map +1 -0
  65. package/dist/i18n/es.js +168 -0
  66. package/dist/i18n/es.js.map +1 -0
  67. package/dist/i18n/index.d.ts +36 -0
  68. package/dist/i18n/index.d.ts.map +1 -0
  69. package/dist/i18n/index.js +50 -0
  70. package/dist/i18n/index.js.map +1 -0
  71. package/dist/i18n/types.d.ts +180 -0
  72. package/dist/i18n/types.d.ts.map +1 -0
  73. package/dist/i18n/types.js +11 -0
  74. package/dist/i18n/types.js.map +1 -0
  75. package/dist/index.d.ts +66 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +69 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/llm.d.ts +226 -0
  80. package/dist/llm.d.ts.map +1 -0
  81. package/dist/llm.js +485 -0
  82. package/dist/llm.js.map +1 -0
  83. package/dist/nearest.d.ts +20 -0
  84. package/dist/nearest.d.ts.map +1 -0
  85. package/dist/nearest.js +54 -0
  86. package/dist/nearest.js.map +1 -0
  87. package/dist/net.d.ts +90 -0
  88. package/dist/net.d.ts.map +1 -0
  89. package/dist/net.js +203 -0
  90. package/dist/net.js.map +1 -0
  91. package/dist/node.d.ts +32 -0
  92. package/dist/node.d.ts.map +1 -0
  93. package/dist/node.js +33 -0
  94. package/dist/node.js.map +1 -0
  95. package/dist/openrouter.d.ts +25 -0
  96. package/dist/openrouter.d.ts.map +1 -0
  97. package/dist/openrouter.js +72 -0
  98. package/dist/openrouter.js.map +1 -0
  99. package/dist/optimize.d.ts +38 -0
  100. package/dist/optimize.d.ts.map +1 -0
  101. package/dist/optimize.js +183 -0
  102. package/dist/optimize.js.map +1 -0
  103. package/dist/otlp.d.ts +91 -0
  104. package/dist/otlp.d.ts.map +1 -0
  105. package/dist/otlp.js +102 -0
  106. package/dist/otlp.js.map +1 -0
  107. package/dist/phrases.d.ts +169 -0
  108. package/dist/phrases.d.ts.map +1 -0
  109. package/dist/phrases.js +939 -0
  110. package/dist/phrases.js.map +1 -0
  111. package/dist/pricing-overlay.d.ts +55 -0
  112. package/dist/pricing-overlay.d.ts.map +1 -0
  113. package/dist/pricing-overlay.js +241 -0
  114. package/dist/pricing-overlay.js.map +1 -0
  115. package/dist/pricing.d.ts +115 -0
  116. package/dist/pricing.d.ts.map +1 -0
  117. package/dist/pricing.js +400 -0
  118. package/dist/pricing.js.map +1 -0
  119. package/dist/profile.d.ts +71 -0
  120. package/dist/profile.d.ts.map +1 -0
  121. package/dist/profile.js +55 -0
  122. package/dist/profile.js.map +1 -0
  123. package/dist/promptfoo.d.ts +58 -0
  124. package/dist/promptfoo.d.ts.map +1 -0
  125. package/dist/promptfoo.js +149 -0
  126. package/dist/promptfoo.js.map +1 -0
  127. package/dist/prune.d.ts +91 -0
  128. package/dist/prune.d.ts.map +1 -0
  129. package/dist/prune.js +110 -0
  130. package/dist/prune.js.map +1 -0
  131. package/dist/reorder.d.ts +82 -0
  132. package/dist/reorder.d.ts.map +1 -0
  133. package/dist/reorder.js +215 -0
  134. package/dist/reorder.js.map +1 -0
  135. package/dist/review.d.ts +54 -0
  136. package/dist/review.d.ts.map +1 -0
  137. package/dist/review.js +131 -0
  138. package/dist/review.js.map +1 -0
  139. package/dist/rules.d.ts +5 -0
  140. package/dist/rules.d.ts.map +1 -0
  141. package/dist/rules.js +279 -0
  142. package/dist/rules.js.map +1 -0
  143. package/dist/savings.d.ts +36 -0
  144. package/dist/savings.d.ts.map +1 -0
  145. package/dist/savings.js +83 -0
  146. package/dist/savings.js.map +1 -0
  147. package/dist/segment.d.ts +8 -0
  148. package/dist/segment.d.ts.map +1 -0
  149. package/dist/segment.js +74 -0
  150. package/dist/segment.js.map +1 -0
  151. package/dist/shared-prefix.d.ts +63 -0
  152. package/dist/shared-prefix.d.ts.map +1 -0
  153. package/dist/shared-prefix.js +151 -0
  154. package/dist/shared-prefix.js.map +1 -0
  155. package/dist/similarity.d.ts +13 -0
  156. package/dist/similarity.d.ts.map +1 -0
  157. package/dist/similarity.js +30 -0
  158. package/dist/similarity.js.map +1 -0
  159. package/dist/structure.d.ts +144 -0
  160. package/dist/structure.d.ts.map +1 -0
  161. package/dist/structure.js +455 -0
  162. package/dist/structure.js.map +1 -0
  163. package/dist/suggest.d.ts +100 -0
  164. package/dist/suggest.d.ts.map +1 -0
  165. package/dist/suggest.js +151 -0
  166. package/dist/suggest.js.map +1 -0
  167. package/dist/tokenizer.d.ts +57 -0
  168. package/dist/tokenizer.d.ts.map +1 -0
  169. package/dist/tokenizer.js +157 -0
  170. package/dist/tokenizer.js.map +1 -0
  171. package/dist/types.d.ts +296 -0
  172. package/dist/types.d.ts.map +1 -0
  173. package/dist/types.js +3 -0
  174. package/dist/types.js.map +1 -0
  175. package/dist/walk.d.ts +40 -0
  176. package/dist/walk.d.ts.map +1 -0
  177. package/dist/walk.js +95 -0
  178. package/dist/walk.js.map +1 -0
  179. package/package.json +56 -0
  180. package/src/advisories.ts +431 -0
  181. package/src/aws-sigv4.ts +174 -0
  182. package/src/baseline.ts +390 -0
  183. package/src/cache.ts +54 -0
  184. package/src/changes.ts +158 -0
  185. package/src/compare.ts +131 -0
  186. package/src/config-schema.ts +451 -0
  187. package/src/config.ts +161 -0
  188. package/src/detect.ts +312 -0
  189. package/src/evaluate.ts +188 -0
  190. package/src/extract.ts +336 -0
  191. package/src/gcp-auth.ts +166 -0
  192. package/src/glob.ts +160 -0
  193. package/src/host.ts +90 -0
  194. package/src/i18n/en.ts +236 -0
  195. package/src/i18n/es.ts +236 -0
  196. package/src/i18n/index.ts +68 -0
  197. package/src/i18n/types.ts +230 -0
  198. package/src/index.ts +228 -0
  199. package/src/llm.ts +708 -0
  200. package/src/nearest.ts +61 -0
  201. package/src/net.ts +233 -0
  202. package/src/node.ts +63 -0
  203. package/src/openrouter.ts +125 -0
  204. package/src/optimize.ts +228 -0
  205. package/src/otlp.ts +179 -0
  206. package/src/phrases.ts +1047 -0
  207. package/src/pricing-overlay.ts +319 -0
  208. package/src/pricing.ts +468 -0
  209. package/src/profile.ts +124 -0
  210. package/src/promptfoo.ts +213 -0
  211. package/src/prune.ts +211 -0
  212. package/src/reorder.ts +307 -0
  213. package/src/review.ts +180 -0
  214. package/src/rules.ts +324 -0
  215. package/src/savings.ts +121 -0
  216. package/src/segment.ts +106 -0
  217. package/src/shared-prefix.ts +198 -0
  218. package/src/similarity.ts +28 -0
  219. package/src/structure.ts +652 -0
  220. package/src/suggest.ts +254 -0
  221. package/src/tokenizer.ts +190 -0
  222. package/src/types.ts +323 -0
  223. package/src/walk.ts +117 -0
package/dist/rules.js ADDED
@@ -0,0 +1,279 @@
1
+ import { EMPHASIS_PREFIXES, FILLER, HEDGES, INTENSIFIERS, POLITENESS, SELF_CHECK, SHOUTED_WORDS, VERBOSE_PHRASES, } from './phrases.js';
2
+ import { jaccard, normalizeForCompare } from './similarity.js';
3
+ import { EXAMPLE_FIELD_LINE } from './structure.js';
4
+ /** Escapes a literal so it can go inside a regular expression. */
5
+ function escapeRe(literal) {
6
+ return literal.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
7
+ }
8
+ /**
9
+ * Builds a regex with accent-aware word boundaries.
10
+ * JavaScript's `\b` only knows ASCII, so "de" would match inside "ánde".
11
+ * Lookarounds over \p{L} avoid that.
12
+ */
13
+ function phraseRegex(phrase, flags = 'giu') {
14
+ const body = escapeRe(phrase).replace(/\s+/g, '\\s+');
15
+ return new RegExp(`(?<![\\p{L}\\p{N}_])${body}(?![\\p{L}\\p{N}_])`, flags);
16
+ }
17
+ /** Cleans up what deleting a phrase leaves behind: spaces and orphan punctuation. */
18
+ function tidyAfterRemoval(text) {
19
+ return (text
20
+ // Repeated spaces inside a line (leading indentation untouched).
21
+ .replace(/([^\n\S])[^\S\n]+/g, '$1')
22
+ // Comma or semicolon left with a space in front of it.
23
+ .replace(/[^\S\n]+([,;:.!?])/g, '$1')
24
+ // Commas duplicated by the removal.
25
+ .replace(/,(\s*,)+/g, ',')
26
+ // Punctuation left at the start of a line.
27
+ .replace(/^[^\S\n]*[,;:]\s*/gm, '')
28
+ // Spanish opening exclamation/question mark left with no content.
29
+ .replace(/[¡¿]+(?=[^\S\n]*(?:[!?.,;:]|$))/gm, '')
30
+ // Duplicated sentence-final punctuation when two fragments join ("answer.!").
31
+ .replace(/([.!?])[^\S\n]*[.!?]+/g, '$1')
32
+ // A line left holding nothing but punctuation.
33
+ .replace(/^[^\S\n]*[.,;:!?¡¿]+[^\S\n]*$/gm, '')
34
+ // A single leading space is removal residue, not indentation.
35
+ .replace(/^[^\S\n](?=\S)/gm, '')
36
+ // Trailing spaces.
37
+ .replace(/[^\S\n]+$/gm, '')
38
+ // Restore the capital when the removal left a sentence starting lowercase.
39
+ // Sentence starts: beginning of the text, after strong punctuation, and
40
+ // the start of a new paragraph.
41
+ .replace(/(^|[.!?]\s+|\n\n[^\S\n]*)(\p{Ll})/gu, (_m, pre, ch) => pre + ch.toUpperCase()));
42
+ }
43
+ /**
44
+ * Deletion regex: on top of the phrase, it swallows the commas that delimited
45
+ * it. Without this, removing an aside such as "if you don't mind," leaves
46
+ * "and, classify it".
47
+ */
48
+ function dropRegex(phrase) {
49
+ const body = escapeRe(phrase).replace(/\s+/g, '\\s+');
50
+ // The boundary check goes AFTER the optional comma: in front of it, the "y"
51
+ // of "y, si no te importa," would fail the match and the comma would stay.
52
+ return new RegExp(`(?:,[^\\S\\n]*)?(?<![\\p{L}\\p{N}_])${body}[^\\S\\n]*,?(?![\\p{L}\\p{N}_])`, 'giu');
53
+ }
54
+ /** A rule that deletes a set of phrases. */
55
+ function dropRule(id, level, phrases) {
56
+ // Longest first, so "thank you very much" wins over "thanks".
57
+ const sorted = [...phrases].sort((a, b) => b.length - a.length);
58
+ const regexes = sorted.map((p) => dropRegex(p));
59
+ return {
60
+ id,
61
+ level,
62
+ apply(text) {
63
+ let hits = 0;
64
+ let out = text;
65
+ for (const re of regexes) {
66
+ out = out.replace(re, () => {
67
+ hits++;
68
+ return '';
69
+ });
70
+ }
71
+ return { text: hits > 0 ? tidyAfterRemoval(out) : out, hits };
72
+ },
73
+ };
74
+ }
75
+ /** Keeps the original leading capital when substituting. */
76
+ function matchCase(original, replacement) {
77
+ if (!replacement)
78
+ return replacement;
79
+ const firstChar = original[0];
80
+ if (firstChar && firstChar === firstChar.toUpperCase() && firstChar !== firstChar.toLowerCase()) {
81
+ return replacement[0].toUpperCase() + replacement.slice(1);
82
+ }
83
+ return replacement;
84
+ }
85
+ const whitespaceRule = {
86
+ id: 'whitespace',
87
+ level: 'safe',
88
+ apply(text) {
89
+ let hits = 0;
90
+ let out = text;
91
+ // The lookbehind is load-bearing, not decoration. Without it the engine
92
+ // restarts this match at every position inside a whitespace run, and when
93
+ // the run does not end the line it fails from each one — quadratic, and
94
+ // 17 seconds on a 100 KB line of spaces that the HTTP API happily accepts.
95
+ // Anchoring to the start of a run means each run is tried exactly once.
96
+ out = out.replace(/(?<![^\S\n])[^\S\n]+$/gm, () => {
97
+ hits++;
98
+ return '';
99
+ });
100
+ // Collapse repeated spaces only after the first non-space character.
101
+ out = out.replace(/(\S)[^\S\n]{2,}/g, (_m, ch) => {
102
+ hits++;
103
+ return `${ch} `;
104
+ });
105
+ out = out.replace(/\n{3,}/g, () => {
106
+ hits++;
107
+ return '\n\n';
108
+ });
109
+ out = out.replace(/\t/g, () => {
110
+ hits++;
111
+ return ' ';
112
+ });
113
+ return { text: out, hits };
114
+ },
115
+ };
116
+ const decorationRule = {
117
+ id: 'decoration',
118
+ level: 'safe',
119
+ apply(text) {
120
+ let hits = 0;
121
+ let out = text;
122
+ // A whole line of 8 or more identical decorative characters.
123
+ out = out.replace(/^[^\S\n]*([=\-_*~#])\1{7,}[^\S\n]*$/gm, () => {
124
+ hits++;
125
+ return '';
126
+ });
127
+ // Repeated exclamation or question marks, opening ones included.
128
+ out = out.replace(/([!?¡¿])\1{1,}/g, (_m, ch) => {
129
+ hits++;
130
+ return ch;
131
+ });
132
+ // Clean up the blank lines the deletion just left behind.
133
+ if (hits > 0)
134
+ out = out.replace(/\n{3,}/g, '\n\n');
135
+ return { text: out, hits };
136
+ },
137
+ };
138
+ const verbosePhrasesRule = {
139
+ id: 'verbose-phrases',
140
+ level: 'safe',
141
+ apply(text) {
142
+ let hits = 0;
143
+ let out = text;
144
+ // Longest first so the most specific substitution wins.
145
+ const entries = [...VERBOSE_PHRASES].sort((a, b) => b[0].length - a[0].length);
146
+ for (const [from, to] of entries) {
147
+ const re = phraseRegex(from);
148
+ out = out.replace(re, (match) => {
149
+ hits++;
150
+ return matchCase(match, to);
151
+ });
152
+ }
153
+ return { text: hits > 0 ? tidyAfterRemoval(out) : out, hits };
154
+ },
155
+ };
156
+ const politenessRule = dropRule('politeness', 'safe', POLITENESS);
157
+ const fillerRule = dropRule('filler', 'safe', FILLER);
158
+ const intensifiersRule = dropRule('intensifiers', 'aggressive', INTENSIFIERS);
159
+ const hedgesRule = dropRule('hedges', 'aggressive', HEDGES);
160
+ const selfCheckRule = dropRule('self-check', 'aggressive', SELF_CHECK);
161
+ const emphasisRule = {
162
+ id: 'emphasis',
163
+ level: 'aggressive',
164
+ apply(text) {
165
+ let hits = 0;
166
+ let out = text;
167
+ for (const prefix of EMPHASIS_PREFIXES) {
168
+ const re = new RegExp(`^([^\\S\\n]*)${escapeRe(prefix)}\\s*:\\s*`, 'gm');
169
+ out = out.replace(re, (_m, indent) => {
170
+ hits++;
171
+ return indent;
172
+ });
173
+ }
174
+ for (const word of SHOUTED_WORDS) {
175
+ const re = phraseRegex(word, 'gu'); // case-sensitive: only the shouted form
176
+ out = out.replace(re, (match) => {
177
+ hits++;
178
+ return match.toLowerCase();
179
+ });
180
+ }
181
+ // Dropping "IMPORTANT:" leaves the sentence starting lowercase: recapitalise.
182
+ return { text: hits > 0 ? tidyAfterRemoval(out) : out, hits };
183
+ },
184
+ };
185
+ const duplicateLinesRule = {
186
+ id: 'duplicate-lines',
187
+ level: 'safe',
188
+ apply(text) {
189
+ const lines = text.split('\n');
190
+ const seen = new Set();
191
+ const kept = [];
192
+ let hits = 0;
193
+ for (const line of lines) {
194
+ const normalized = normalizeForCompare(line);
195
+ // A labelled example field is data, not repetition: two examples sharing
196
+ // an output line show that two inputs map to the same answer. Dropping
197
+ // the second leaves that example with no output.
198
+ if (normalized.length >= 25 && !EXAMPLE_FIELD_LINE.test(line)) {
199
+ if (seen.has(normalized)) {
200
+ hits++;
201
+ continue;
202
+ }
203
+ seen.add(normalized);
204
+ }
205
+ kept.push(line);
206
+ }
207
+ let out = kept.join('\n');
208
+ if (hits > 0)
209
+ out = out.replace(/\n{3,}/g, '\n\n');
210
+ return { text: out, hits };
211
+ },
212
+ };
213
+ const duplicateBlocksRule = {
214
+ id: 'duplicate-blocks',
215
+ level: 'safe',
216
+ apply(text) {
217
+ const blocks = text.split(/\n{2,}/);
218
+ const seen = new Set();
219
+ const kept = [];
220
+ let hits = 0;
221
+ for (const block of blocks) {
222
+ const normalized = normalizeForCompare(block);
223
+ if (normalized.length >= 40) {
224
+ if (seen.has(normalized)) {
225
+ hits++;
226
+ continue;
227
+ }
228
+ seen.add(normalized);
229
+ }
230
+ kept.push(block);
231
+ }
232
+ return { text: kept.join('\n\n'), hits };
233
+ },
234
+ };
235
+ const nearDuplicateBlocksRule = {
236
+ id: 'near-duplicate-blocks',
237
+ level: 'aggressive',
238
+ apply(text) {
239
+ const blocks = text.split(/\n{2,}/);
240
+ const keptNormalized = [];
241
+ const kept = [];
242
+ let hits = 0;
243
+ for (const block of blocks) {
244
+ const normalized = normalizeForCompare(block);
245
+ if (normalized.length >= 60) {
246
+ const isDuplicate = keptNormalized.some((prev) => jaccard(prev, normalized) >= 0.92);
247
+ if (isDuplicate) {
248
+ hits++;
249
+ continue;
250
+ }
251
+ keptNormalized.push(normalized);
252
+ }
253
+ kept.push(block);
254
+ }
255
+ return { text: kept.join('\n\n'), hits };
256
+ },
257
+ };
258
+ /** Every rule, in the order they must run. */
259
+ export const RULES = [
260
+ // Whole-block deletions first, so the rest works over less text.
261
+ duplicateBlocksRule,
262
+ nearDuplicateBlocksRule,
263
+ duplicateLinesRule,
264
+ // Then the phrase-level rules.
265
+ verbosePhrasesRule,
266
+ politenessRule,
267
+ fillerRule,
268
+ hedgesRule,
269
+ intensifiersRule,
270
+ selfCheckRule,
271
+ emphasisRule,
272
+ // And finally typographic cleanup, which picks up what the others left.
273
+ decorationRule,
274
+ whitespaceRule,
275
+ ];
276
+ export function getRule(id) {
277
+ return RULES.find((r) => r.id === id);
278
+ }
279
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { PricingCatalogue } from './pricing.js';
2
+ import type { CostBreakdown, SavingsReport, UsageProfile } from './types.js';
3
+ /**
4
+ * Cost of a single call.
5
+ *
6
+ * `batchDiscount` defaults to Anthropic's 50% rather than being read from the
7
+ * model, because this function takes prices rather than a model. Callers that
8
+ * have the model — which is all of them inside this package — pass its own,
9
+ * and a provider with no batch API passes 1.
10
+ */
11
+ export declare function costOfCall(inputTokens: number, outputTokens: number, inputPerMTok: number, outputPerMTok: number, batch: boolean, batchDiscount?: number): CostBreakdown;
12
+ /**
13
+ * Compares cost before and after optimising.
14
+ *
15
+ * Important: output tokens are held constant. A shorter prompt often produces
16
+ * somewhat shorter answers, but that depends on the task and cannot be
17
+ * promised, so the saving reported here comes exclusively from input tokens.
18
+ */
19
+ export declare function computeSavings(tokensBefore: number, tokensAfter: number, usage: UsageProfile, on?: Date, pricing?: PricingCatalogue): SavingsReport;
20
+ /**
21
+ * Formats a USD amount with precision suited to its magnitude.
22
+ * Currency formatting stays in `en-US` on purpose: these are US dollar prices
23
+ * from a US price list, and showing them the same way everywhere avoids
24
+ * confusion when a report is shared across locales.
25
+ */
26
+ export declare function formatUsd(value: number): string;
27
+ /**
28
+ * Formats a USD amount that can legitimately be negative, with an explicit
29
+ * sign.
30
+ *
31
+ * `formatUsd` renders a negative as `$-30.80`, which reads as a typo. In a
32
+ * comparison every cost line can go either way and the sign carries the whole
33
+ * meaning, so it goes in front of the currency where a reader expects it.
34
+ */
35
+ export declare function formatSignedUsd(value: number): string;
36
+ //# sourceMappingURL=savings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"savings.d.ts","sourceRoot":"","sources":["../src/savings.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE7E;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,OAAO,EACd,aAAa,GAAE,MAA+B,GAC7C,aAAa,CAKf;AAUD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,YAAY,EACnB,EAAE,GAAE,IAAiB,EACrB,OAAO,GAAE,gBAAoC,GAC5C,aAAa,CAsCf;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO/C;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIrD"}
@@ -0,0 +1,83 @@
1
+ import { BUNDLED_CATALOGUE, COST_MULTIPLIERS, effectivePricing, modelFrom, multipliersFor, } from './pricing.js';
2
+ /**
3
+ * Cost of a single call.
4
+ *
5
+ * `batchDiscount` defaults to Anthropic's 50% rather than being read from the
6
+ * model, because this function takes prices rather than a model. Callers that
7
+ * have the model — which is all of them inside this package — pass its own,
8
+ * and a provider with no batch API passes 1.
9
+ */
10
+ export function costOfCall(inputTokens, outputTokens, inputPerMTok, outputPerMTok, batch, batchDiscount = COST_MULTIPLIERS.batch) {
11
+ const discount = batch ? batchDiscount : 1;
12
+ const inputUsd = (inputTokens / 1_000_000) * inputPerMTok * discount;
13
+ const outputUsd = (outputTokens / 1_000_000) * outputPerMTok * discount;
14
+ return { inputUsd, outputUsd, totalUsd: inputUsd + outputUsd };
15
+ }
16
+ function scale(cost, factor) {
17
+ return {
18
+ inputUsd: cost.inputUsd * factor,
19
+ outputUsd: cost.outputUsd * factor,
20
+ totalUsd: cost.totalUsd * factor,
21
+ };
22
+ }
23
+ /**
24
+ * Compares cost before and after optimising.
25
+ *
26
+ * Important: output tokens are held constant. A shorter prompt often produces
27
+ * somewhat shorter answers, but that depends on the task and cannot be
28
+ * promised, so the saving reported here comes exclusively from input tokens.
29
+ */
30
+ export function computeSavings(tokensBefore, tokensAfter, usage, on = new Date(), pricing = BUNDLED_CATALOGUE) {
31
+ const model = modelFrom(pricing, usage.model);
32
+ const { inputPerMTok, outputPerMTok, promoApplied } = effectivePricing(model, on);
33
+ // A provider with no batch API gets no discount even when the caller ticked
34
+ // the box: `batchEligible` describes the work, not what the provider sells.
35
+ const batchDiscount = multipliersFor(model).batch ?? 1;
36
+ const before = costOfCall(tokensBefore, usage.avgOutputTokens, inputPerMTok, outputPerMTok, usage.batchEligible, batchDiscount);
37
+ const after = costOfCall(tokensAfter, usage.avgOutputTokens, inputPerMTok, outputPerMTok, usage.batchEligible, batchDiscount);
38
+ const monthBefore = scale(before, usage.callsPerMonth);
39
+ const monthAfter = scale(after, usage.callsPerMonth);
40
+ const monthlySavingsUsd = monthBefore.totalUsd - monthAfter.totalUsd;
41
+ return {
42
+ model: model.id,
43
+ modelDisplayName: model.displayName,
44
+ promoApplied,
45
+ perCall: { before, after },
46
+ perMonth: { before: monthBefore, after: monthAfter },
47
+ monthlySavingsUsd,
48
+ monthlySavingsPct: monthBefore.totalUsd > 0 ? (monthlySavingsUsd / monthBefore.totalUsd) * 100 : 0,
49
+ };
50
+ }
51
+ /**
52
+ * Formats a USD amount with precision suited to its magnitude.
53
+ * Currency formatting stays in `en-US` on purpose: these are US dollar prices
54
+ * from a US price list, and showing them the same way everywhere avoids
55
+ * confusion when a report is shared across locales.
56
+ */
57
+ export function formatUsd(value) {
58
+ if (value === 0)
59
+ return '$0';
60
+ const abs = Math.abs(value);
61
+ if (abs < 0.01)
62
+ return `$${value.toFixed(5)}`;
63
+ if (abs < 1)
64
+ return `$${value.toFixed(4)}`;
65
+ if (abs < 1000)
66
+ return `$${value.toFixed(2)}`;
67
+ return `$${value.toLocaleString('en-US', { maximumFractionDigits: 0 })}`;
68
+ }
69
+ /**
70
+ * Formats a USD amount that can legitimately be negative, with an explicit
71
+ * sign.
72
+ *
73
+ * `formatUsd` renders a negative as `$-30.80`, which reads as a typo. In a
74
+ * comparison every cost line can go either way and the sign carries the whole
75
+ * meaning, so it goes in front of the currency where a reader expects it.
76
+ */
77
+ export function formatSignedUsd(value) {
78
+ if (value === 0)
79
+ return '$0';
80
+ const rendered = formatUsd(Math.abs(value));
81
+ return value > 0 ? `+${rendered}` : `-${rendered}`;
82
+ }
83
+ //# sourceMappingURL=savings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"savings.js","sourceRoot":"","sources":["../src/savings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { Segment } from './types.js';
2
+ /** Splits the text into segments, marking what must not be modified. */
3
+ export declare function segment(text: string): Segment[];
4
+ /** Joins segments back into a single string. */
5
+ export declare function join(segments: Segment[]): string;
6
+ /** Returns only the text of the protected segments, in order. */
7
+ export declare function protectedTexts(segments: Segment[]): string[];
8
+ //# sourceMappingURL=segment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segment.d.ts","sourceRoot":"","sources":["../src/segment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,OAAO,EAAE,MAAM,YAAY,CAAC;AA6C1D,wEAAwE;AACxE,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAiD/C;AAED,gDAAgD;AAChD,wBAAgB,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAEhD;AAED,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE,CAE5D"}
@@ -0,0 +1,74 @@
1
+ // Order matters: the first pattern to match at a position wins.
2
+ const PATTERNS = [
3
+ // Fenced code blocks with ``` or ~~~ (unclosed blocks run to the end).
4
+ { kind: 'fenced-code', regex: /(?:```|~~~)[\s\S]*?(?:```|~~~|$)/g },
5
+ // Indented blocks of 4+ spaces are left to the rules: they are ambiguous in markdown.
6
+ { kind: 'url', regex: /\b(?:https?|ftp):\/\/[^\s<>"')\]]+/g, trimTrailing: /[.,;:!?]+$/ },
7
+ // Template placeholders: {{var}}, {var}, ${var}, {% tag %}, <<VAR>>, %(var)s
8
+ {
9
+ kind: 'placeholder',
10
+ regex: /\{\{[^{}]*\}\}|\{%[\s\S]*?%\}|\$\{[^{}]*\}|<<[A-Z0-9_]+>>|%\([^()]*\)[sdfr]|\{[A-Za-z_][A-Za-z0-9_.]*\}/g,
11
+ },
12
+ // Inline code spans.
13
+ { kind: 'inline-code', regex: /`[^`\n]+`/g },
14
+ // XML/HTML tags, heavily used to structure prompts.
15
+ { kind: 'xml-tag', regex: /<\/?[A-Za-z][\w:.-]*(?:\s[^<>]*?)?\/?>/g },
16
+ ];
17
+ /** Splits the text into segments, marking what must not be modified. */
18
+ export function segment(text) {
19
+ const matches = [];
20
+ for (const { kind, regex, trimTrailing } of PATTERNS) {
21
+ // Copy the regex so lastIndex is not shared between calls.
22
+ const re = new RegExp(regex.source, regex.flags);
23
+ let m;
24
+ while ((m = re.exec(text)) !== null) {
25
+ let matched = m[0];
26
+ if (matched.length === 0) {
27
+ re.lastIndex++;
28
+ continue;
29
+ }
30
+ if (trimTrailing)
31
+ matched = matched.replace(trimTrailing, '');
32
+ if (matched.length === 0)
33
+ continue;
34
+ matches.push({ start: m.index, end: m.index + matched.length, kind });
35
+ }
36
+ }
37
+ // Sort by start and, on a tie, longest span first.
38
+ matches.sort((a, b) => a.start - b.start || b.end - a.end);
39
+ // Drop overlaps, keeping the first match at each position.
40
+ const kept = [];
41
+ let cursor = 0;
42
+ for (const match of matches) {
43
+ if (match.start < cursor)
44
+ continue;
45
+ kept.push(match);
46
+ cursor = match.end;
47
+ }
48
+ const segments = [];
49
+ let pos = 0;
50
+ for (const match of kept) {
51
+ if (match.start > pos) {
52
+ segments.push({ kind: 'mutable', text: text.slice(pos, match.start) });
53
+ }
54
+ segments.push({
55
+ kind: 'protected',
56
+ protection: match.kind,
57
+ text: text.slice(match.start, match.end),
58
+ });
59
+ pos = match.end;
60
+ }
61
+ if (pos < text.length) {
62
+ segments.push({ kind: 'mutable', text: text.slice(pos) });
63
+ }
64
+ return segments;
65
+ }
66
+ /** Joins segments back into a single string. */
67
+ export function join(segments) {
68
+ return segments.map((s) => s.text).join('');
69
+ }
70
+ /** Returns only the text of the protected segments, in order. */
71
+ export function protectedTexts(segments) {
72
+ return segments.filter((s) => s.kind === 'protected').map((s) => s.text);
73
+ }
74
+ //# sourceMappingURL=segment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segment.js","sourceRoot":"","sources":["../src/segment.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import type { TokenCounter } from './types.js';
2
+ /**
3
+ * The prefix length below which unifying buys nothing, and `Infinity` when there
4
+ * is no answer.
5
+ *
6
+ * Lives here rather than in the CLI because it is the gate on this whole report
7
+ * and a gate with no test is decoration. It was a private function in the CLI
8
+ * first; a mutation run deleting the `unknown` branch survived, which is what a
9
+ * function nothing can reach looks like from the outside.
10
+ *
11
+ * `unknown` and a missing minimum both yield `Infinity`, so nothing is reported.
12
+ * That is the direction to fail: telling somebody to unify a preamble across
13
+ * twelve files to enable caching their provider may not offer spends their
14
+ * afternoon, and unlike a wrong number on a report nothing later corrects it.
15
+ */
16
+ export declare function cacheableMinimum(model: {
17
+ caching?: string | null;
18
+ cacheMinTokens?: number | null;
19
+ } | undefined): number;
20
+ export interface PrefixCandidate {
21
+ /** How the report names it. A path, or a path with a marked prompt's id. */
22
+ path: string;
23
+ text: string;
24
+ }
25
+ export interface SharedPrefix {
26
+ /** Every prompt in the group, in the order they were given. */
27
+ paths: string[];
28
+ /** Blocks the group has in common, counted from the start. */
29
+ blocks: number;
30
+ /** Estimated tokens in that prefix, as the first prompt writes it. */
31
+ tokens: number;
32
+ /**
33
+ * What stops the prefixes being byte-identical today.
34
+ *
35
+ * `whitespace` — collapsing runs of whitespace makes them equal, so a
36
+ * formatter fixes it and no wording decision is needed.
37
+ * `wording` — something else differs: capitalisation, punctuation, word order,
38
+ * or genuinely different words. Someone has to choose.
39
+ */
40
+ drift: 'whitespace' | 'wording';
41
+ /** The shared prefix as the first prompt in the group writes it. */
42
+ sample: string;
43
+ }
44
+ export interface SharedPrefixOptions {
45
+ /**
46
+ * The model's cacheable minimum. A shared prefix below it buys nothing, so it
47
+ * is not reported — the same refusal `reorder.ts` makes, for the same reason:
48
+ * a change that recovers nothing is a diff for its own sake.
49
+ */
50
+ minTokens?: number;
51
+ countTokens?: TokenCounter;
52
+ }
53
+ /**
54
+ * Groups of prompts that could share a cache prefix and do not.
55
+ *
56
+ * Grouped by their **first** block, which is not a shortcut. Caching matches from
57
+ * the beginning of the request, so two prompts whose opening paragraphs differ
58
+ * share nothing no matter how identical the rest is — a group keyed on anything
59
+ * later would name prompts that cannot be made to share a prefix without
60
+ * reordering them, which is a different and far more dangerous change.
61
+ */
62
+ export declare function sharedPrefixes(prompts: readonly PrefixCandidate[], options?: SharedPrefixOptions): SharedPrefix[];
63
+ //# sourceMappingURL=shared-prefix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-prefix.d.ts","sourceRoot":"","sources":["../src/shared-prefix.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAmD/C;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,SAAS,GAC7E,MAAM,CAIR;AAED,MAAM,WAAW,eAAe;IAC9B,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,KAAK,EAAE,YAAY,GAAG,SAAS,CAAC;IAChC,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,YAAY,CAAC;CAC5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,SAAS,eAAe,EAAE,EACnC,OAAO,GAAE,mBAAwB,GAChC,YAAY,EAAE,CA0EhB"}