@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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 David Muñoz Rey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,163 @@
1
+ # @trazum/core
2
+
3
+ Prompt optimiser. Shortens what you send to a model without changing what you
4
+ ask for, and tells you what that is worth per month.
5
+
6
+ **Zero runtime dependencies**, and that is asserted in CI rather than promised.
7
+ This library reads your prompts; every dependency would be someone else's code
8
+ reading them too.
9
+
10
+ Part of [Trazum](https://github.com/Davmunrey/Trazum). For the terminal, see
11
+ [`@trazum/cli`](https://www.npmjs.com/package/@trazum/cli).
12
+
13
+ > [!NOTE]
14
+ > **Not published yet.** The command below will not work today — there is no tag
15
+ > and `npm view` returns 404. Run it from source meanwhile:
16
+ > [github.com/Davmunrey/Trazum](https://github.com/Davmunrey/Trazum#getting-started).
17
+
18
+ ```bash
19
+ npm install @trazum/core
20
+ ```
21
+
22
+ ## Use
23
+
24
+ ```ts
25
+ import { optimize } from '@trazum/core';
26
+
27
+ const result = optimize(prompt, {
28
+ level: 'safe', // or 'aggressive' — read the diff
29
+ usage: {
30
+ model: 'claude-opus-5',
31
+ callsPerMonth: 50_000,
32
+ avgOutputTokens: 500,
33
+ cacheHitRate: 0.9,
34
+ batchEligible: false,
35
+ },
36
+ });
37
+
38
+ result.optimized; // the shortened prompt
39
+ result.savings.monthlySavingsUsd; // what that is worth
40
+ result.advisories; // usually where the real money is
41
+ result.rules; // what each rule changed, with samples
42
+ ```
43
+
44
+ The optimisation is **deterministic**: same input, same output, no API key, no
45
+ network call, nothing to pay.
46
+
47
+ ## What it does
48
+
49
+ **Trims with deterministic rules** — courtesy, filler, verbose phrasing,
50
+ duplicated paragraphs, decorative separators, shouting in capitals.
51
+
52
+ **Never touches what would break the prompt.** Code fences, inline code, URLs,
53
+ template placeholders (`{{x}}`, `${x}`, `{x}`, `{% %}`) and XML/HTML tags are
54
+ isolated *before* any rule runs. If a rule ever did make one of those disappear,
55
+ that rule is discarded and the rest carry on.
56
+
57
+ **Tells you where the money actually is.** Trimming is usually not it. Read
58
+ `result.advisories` first — they are sorted warnings-first, then by monthly
59
+ saving:
60
+
61
+ | Advisory | What it finds |
62
+ |---|---|
63
+ | `prompt-caching` | Caching is a byte-for-byte prefix match, so a template only caches up to its first placeholder. The saving is computed over the **real** stable prefix. |
64
+ | `cache-prefix-reorder` | Stable instructions stranded *after* the first placeholder never cache. Often the single largest saving available. |
65
+ | `contradictory-instructions` | "Answer in English" three paragraphs above "reply in the customer's own language". A correctness bug that also costs tokens twice. |
66
+ | `redundant-examples` | Few-shot examples that are near-copies of an earlier one. |
67
+ | `restated-output-format` | A schema shown in a code block and then walked again in prose. |
68
+ | `movable-output-schema` | A schema the request could carry as a parameter instead — cheaper *and* stricter, and the one finding that is not a trade-off. Reported, never edited: it changes the call, not the prompt. |
69
+ | `model-downgrade` | A keyword heuristic, not a quality judgement — validate with evaluations before acting on it. |
70
+ | `output-dominated` | You pay more for the answer than the prompt, so shortening it has a ceiling. |
71
+ | `batch-api`, `below-cache-minimum`, `context-window`, `promotional-pricing` | |
72
+
73
+ The last three structural findings are **advisory only**. A contradiction has a
74
+ right answer only you know, and an example that looks redundant may be
75
+ demonstrating a boundary case on purpose. Trazum points; it does not cut.
76
+
77
+ ## Comparing two versions
78
+
79
+ ```ts
80
+ import { comparePrompts, formatSignedUsd } from '@trazum/core';
81
+
82
+ const change = comparePrompts(before, after, { usage });
83
+
84
+ change.tokenDelta; // +37 (grew)
85
+ formatSignedUsd(change.monthlyDeltaUsd); // "+$9.25"
86
+ change.advisories.appeared; // what the edit broke
87
+ ```
88
+
89
+ **Note the sign.** Everything `comparePrompts` returns is `after - before`, so
90
+ **positive means worse** — the opposite of `result.savings`. That is why it lives
91
+ in its own module and why nothing in it is called a saving.
92
+
93
+ ## Prices
94
+
95
+ The bundled catalogue is correct as of `PRICING_LAST_REVIEWED` and needs no
96
+ setup. When a published price moves, correct it without upgrading this package:
97
+
98
+ ```ts
99
+ import { catalogueFromOverlay, optimize } from '@trazum/core';
100
+
101
+ const pricing = catalogueFromOverlay(`{
102
+ "lastReviewed": "2027-01-15",
103
+ "models": { "claude-opus-5": { "inputPerMTok": 6 } }
104
+ }`);
105
+
106
+ const result = optimize(prompt, { usage, pricing });
107
+ result.pricingSource; // says which models came from the overlay, and its date
108
+ ```
109
+
110
+ A catalogue is a **value**: applying an overlay returns a new one and mutates
111
+ nothing, so your prices never leak into another caller's report.
112
+
113
+ ## Two entry points
114
+
115
+ `@trazum/core` imports **no Node builtins** — enforced by a test that walks the
116
+ import graph, because the browser bundles it. Anything that reads the filesystem
117
+ lives on `@trazum/core/node`:
118
+
119
+ ```ts
120
+ import { loadConfig, walkPrompts } from '@trazum/core/node';
121
+ ```
122
+
123
+ ## Optional LLM pass
124
+
125
+ For the compression rules cannot do. The result is only accepted if it is
126
+ shorter **and** leaves protected content byte-identical — it never returns
127
+ something worse than where it started.
128
+
129
+ ```ts
130
+ import { openAiCompatible, refineWithLlm } from '@trazum/core';
131
+
132
+ const refined = await refineWithLlm(result, openAiCompatible({
133
+ baseUrl: 'https://your-llm/v1',
134
+ apiKey: process.env.LLM_KEY,
135
+ model: 'your-model',
136
+ }));
137
+ ```
138
+
139
+ ## Token counts
140
+
141
+ The bundled estimator is a dependency-free heuristic, accurate to about **±15%**
142
+ on ordinary prose, and it compares two versions of the same prompt well — which
143
+ is what it is for. For exact figures, pass a real counter:
144
+
145
+ ```ts
146
+ import { countTokensAnthropic, withExactTokenCounts } from '@trazum/core';
147
+
148
+ const exact = await withExactTokenCounts(result, countTokensAnthropic({ apiKey }));
149
+ ```
150
+
151
+ ## Locales
152
+
153
+ English and Spanish. **A locale changes the report, never the optimisation** —
154
+ the same prompt in any locale produces the same optimised text, the same token
155
+ counts and the same advisory ids. Match on ids, not on prose.
156
+
157
+ ## Versioning
158
+
159
+ See [VERSIONING.md](https://github.com/Davmunrey/Trazum/blob/main/VERSIONING.md).
160
+ Rule and advisory **identifiers** are stable across locales and versions on
161
+ purpose; message **text** is copy and gets improved.
162
+
163
+ MIT.
@@ -0,0 +1,23 @@
1
+ import type { Locale } from './i18n/types.js';
2
+ import type { PricingCatalogue } from './pricing.js';
3
+ import type { Advisory, ModelPricing, TokenCounter, UsageProfile } from './types.js';
4
+ /**
5
+ * Estimates the capability tier the prompt needs.
6
+ *
7
+ * This is a keyword-and-size heuristic, not a judgement about answer quality.
8
+ * Treat it as a hypothesis to validate with your own evaluations before
9
+ * moving down a tier in production.
10
+ */
11
+ export declare function recommendTier(prompt: string, tokens: number): ModelPricing['tier'];
12
+ export interface AdvisoryOptions {
13
+ /** Reference date, used to decide whether a promotional price is live. */
14
+ on?: Date;
15
+ /** Token counter, so the cache-prefix analysis matches the caller's. */
16
+ count?: TokenCounter;
17
+ locale?: Locale;
18
+ /** Prices to work from. Defaults to the catalogue bundled with this release. */
19
+ pricing?: PricingCatalogue;
20
+ }
21
+ /** Builds the advisories that do not modify the prompt but do move the bill. */
22
+ export declare function buildAdvisories(optimizedPrompt: string, tokensAfter: number, usage: UsageProfile, options?: AdvisoryOptions): Advisory[];
23
+ //# sourceMappingURL=advisories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"advisories.d.ts","sourceRoot":"","sources":["../src/advisories.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAG9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKrD,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAQrF;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAclF;AAsDD,MAAM,WAAW,eAAe;IAC9B,0EAA0E;IAC1E,EAAE,CAAC,EAAE,IAAI,CAAC;IACV,wEAAwE;IACxE,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC5B;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAC7B,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,YAAY,EACnB,OAAO,GAAE,eAAoB,GAC5B,QAAQ,EAAE,CAiUZ"}
@@ -0,0 +1,376 @@
1
+ import { analyzeCachePrefix } from './cache.js';
2
+ import { getMessages } from './i18n/index.js';
3
+ import { COMPLEX_SIGNALS, SIMPLE_SIGNALS } from './phrases.js';
4
+ import { BUNDLED_CATALOGUE, effectivePricing, modelFrom, multipliersFor } from './pricing.js';
5
+ import { formatUsd } from './savings.js';
6
+ import { analyzeExamples, findContradictions, findMovableSchema, findRestatedFormat } from './structure.js';
7
+ import { estimateTokens } from './tokenizer.js';
8
+ function countSignals(haystack, signals) {
9
+ let count = 0;
10
+ for (const signal of signals)
11
+ if (haystack.includes(signal))
12
+ count++;
13
+ return count;
14
+ }
15
+ /**
16
+ * Estimates the capability tier the prompt needs.
17
+ *
18
+ * This is a keyword-and-size heuristic, not a judgement about answer quality.
19
+ * Treat it as a hypothesis to validate with your own evaluations before
20
+ * moving down a tier in production.
21
+ */
22
+ export function recommendTier(prompt, tokens) {
23
+ const haystack = prompt.toLowerCase();
24
+ let score = countSignals(haystack, COMPLEX_SIGNALS) * 2 - countSignals(haystack, SIMPLE_SIGNALS) * 2;
25
+ if (tokens > 4000)
26
+ score += 2;
27
+ else if (tokens > 1500)
28
+ score += 1;
29
+ else if (tokens < 300)
30
+ score -= 1;
31
+ if (/```|~~~/.test(prompt))
32
+ score += 1;
33
+ if (score >= 3)
34
+ return 'opus';
35
+ if (score >= 0)
36
+ return 'sonnet';
37
+ return 'haiku';
38
+ }
39
+ const TIER_ORDER = {
40
+ /**
41
+ * Below every real tier, so no tier is ever "less capable than unknown".
42
+ *
43
+ * The value carries the intent rather than the guard below carrying it alone.
44
+ * Set the other way — above everything — the comparison
45
+ * `TIER_ORDER[suggested] < TIER_ORDER['unknown']` is true for every prompt,
46
+ * and the only thing left standing between that and a recommendation is an
47
+ * unrelated provider filter. Mutation testing found exactly that: deleting
48
+ * the guard changed no test result, because a second accident was covering
49
+ * for it. Two accidents in a row is not a design.
50
+ */
51
+ unknown: Number.NEGATIVE_INFINITY,
52
+ haiku: 0,
53
+ sonnet: 1,
54
+ opus: 2,
55
+ frontier: 3,
56
+ };
57
+ /**
58
+ * Cheapest model of a capability tier, using today's effective input price.
59
+ *
60
+ * **Within the same provider**, which is a product decision rather than an
61
+ * implementation detail. Dropping from Opus to Sonnet is a one-line change;
62
+ * moving to another vendor is a different API, different behaviour and a
63
+ * migration. This advisory is already caveated as a keyword heuristic rather
64
+ * than a judgement about answer quality, and a keyword heuristic has no business
65
+ * recommending that somebody change supplier.
66
+ *
67
+ * A model with no provider recorded only matches others with none, so an overlay
68
+ * that adds a bare model cannot pull a switch out of thin air.
69
+ */
70
+ function cheapestInTier(tier, on, pricing, provider) {
71
+ const candidates = pricing.models.filter((m) => m.tier === tier &&
72
+ m.provider === provider &&
73
+ // Not generally available: recommending a model the reader cannot call is
74
+ // worse than recommending nothing.
75
+ m.recommendable !== false);
76
+ if (candidates.length === 0)
77
+ return undefined;
78
+ return candidates.reduce((best, m) => effectivePricing(m, on).inputPerMTok < effectivePricing(best, on).inputPerMTok ? m : best);
79
+ }
80
+ /** Builds the advisories that do not modify the prompt but do move the bill. */
81
+ export function buildAdvisories(optimizedPrompt, tokensAfter, usage, options = {}) {
82
+ const { on = new Date(), count = estimateTokens, locale, pricing = BUNDLED_CATALOGUE } = options;
83
+ const t = getMessages(locale);
84
+ const advisories = [];
85
+ const model = modelFrom(pricing, usage.model);
86
+ const { inputPerMTok, outputPerMTok, promoApplied } = effectivePricing(model, on);
87
+ // Per model, not global. A cache read is ~10% of input on Anthropic and about
88
+ // half on OpenAI; one constant for both overstates an OpenAI caching saving
89
+ // fivefold, which is an invented saving rather than an imprecise one.
90
+ const rates = multipliersFor(model);
91
+ const batchFactor = usage.batchEligible ? (rates.batch ?? 1) : 1;
92
+ const monthlyInputUsd = (tokensAfter / 1_000_000) * inputPerMTok * usage.callsPerMonth * batchFactor;
93
+ const monthlyOutputUsd = (usage.avgOutputTokens / 1_000_000) * outputPerMTok * usage.callsPerMonth * batchFactor;
94
+ // --- Context window ---
95
+ if (tokensAfter > model.contextWindow) {
96
+ advisories.push({
97
+ id: 'context-overflow',
98
+ severity: 'warning',
99
+ ...t.advisories.contextOverflow({
100
+ tokens: tokensAfter,
101
+ modelName: model.displayName,
102
+ contextWindow: model.contextWindow,
103
+ }),
104
+ estimatedMonthlyUsd: null,
105
+ });
106
+ }
107
+ // --- Prompt caching ---
108
+ // Caching is a prefix match: in a template with placeholders, only what
109
+ // precedes the first placeholder is cached. Costing the whole prompt would
110
+ // be a lie the moment {{x}} takes a different value between calls.
111
+ const cache = analyzeCachePrefix(optimizedPrompt, count);
112
+ // A provider with no prompt caching gets no caching advice. Without this the
113
+ // zero minimum satisfies `0 >= 0` and Trazum offers a saving that cannot be
114
+ // bought at any price — the exact failure that moving the multipliers onto the
115
+ // model was meant to prevent, reintroduced one field along.
116
+ /**
117
+ * Three ways to have nothing useful to say about caching, and only one of
118
+ * them used to be handled.
119
+ *
120
+ * `none` is a fact: the provider does not cache, so advice would be a saving
121
+ * nobody can buy. `unknown` is the absence of a fact — a catalogue built from
122
+ * a live price feed knows what a model costs and not how it caches — and it
123
+ * has to decline for the opposite reason: not because the answer is no, but
124
+ * because nobody asked anyone. Guessing either way is a number in somebody's
125
+ * budget that came from nowhere.
126
+ */
127
+ const cachingKnown = model.caching !== 'none' && model.caching !== 'unknown' && model.cacheMinTokens !== null;
128
+ if (usage.callsPerMonth > 1 && cachingKnown) {
129
+ const prefixShare = tokensAfter > 0 ? cache.stablePrefixTokens / tokensAfter : 0;
130
+ const monthlyPrefixUsd = monthlyInputUsd * Math.min(1, prefixShare);
131
+ const hitRate = Math.min(Math.max(usage.cacheHitRate, 0), 1);
132
+ const factor = (1 - hitRate) * rates.cacheWrite5m + hitRate * rates.cacheRead;
133
+ const minTokens = model.cacheMinTokens ?? 0;
134
+ if (cache.stablePrefixTokens >= minTokens) {
135
+ const saving = monthlyPrefixUsd * (1 - factor);
136
+ if (saving > 0) {
137
+ advisories.push({
138
+ id: 'prompt-caching',
139
+ severity: 'opportunity',
140
+ ...t.advisories.promptCaching({
141
+ placeholder: cache.firstPlaceholder,
142
+ prefixTokens: cache.stablePrefixTokens,
143
+ totalTokens: tokensAfter,
144
+ minTokens,
145
+ modelName: model.displayName,
146
+ hitRatePct: Math.round(hitRate * 100),
147
+ readPct: Math.round(rates.cacheRead * 100),
148
+ writePct: Math.round(rates.cacheWrite5m * 100),
149
+ explicit: (model.caching ?? 'explicit') === 'explicit',
150
+ }),
151
+ estimatedMonthlyUsd: saving,
152
+ });
153
+ }
154
+ else {
155
+ advisories.push({
156
+ id: 'prompt-caching-not-worth-it',
157
+ severity: 'info',
158
+ ...t.advisories.promptCachingNotWorthIt(),
159
+ estimatedMonthlyUsd: null,
160
+ });
161
+ }
162
+ }
163
+ else {
164
+ advisories.push({
165
+ id: 'below-cache-minimum',
166
+ severity: 'info',
167
+ ...t.advisories.belowCacheMinimum({
168
+ modelName: model.displayName,
169
+ minTokens,
170
+ placeholder: cache.firstPlaceholder,
171
+ prefixTokens: cache.stablePrefixTokens,
172
+ totalTokens: tokensAfter,
173
+ mentionLowerMinimum: minTokens > 512,
174
+ }),
175
+ estimatedMonthlyUsd: null,
176
+ });
177
+ }
178
+ // Stable content placed AFTER the first placeholder: never cached today,
179
+ // but moving it in front would make it cacheable.
180
+ if (cache.firstPlaceholder &&
181
+ cache.staticTokensAfter >= 200 &&
182
+ cache.staticTokensAfter >= tokensAfter * 0.3) {
183
+ const movableShare = tokensAfter > 0 ? cache.staticTokensAfter / tokensAfter : 0;
184
+ const saving = monthlyInputUsd * movableShare * Math.max(0, 1 - factor);
185
+ advisories.push({
186
+ id: 'cache-prefix-reorder',
187
+ severity: 'opportunity',
188
+ ...t.advisories.cachePrefixReorder({
189
+ staticTokensAfter: cache.staticTokensAfter,
190
+ sharePct: Math.round(movableShare * 100),
191
+ placeholder: cache.firstPlaceholder,
192
+ }),
193
+ estimatedMonthlyUsd: saving > 0 ? saving : null,
194
+ });
195
+ }
196
+ }
197
+ // --- Batch API ---
198
+ // `rates.batch === null` means the provider has no batch API at all, which is
199
+ // different from not having said: advising a discount that cannot be bought
200
+ // is worse than staying quiet.
201
+ if (!usage.batchEligible && usage.callsPerMonth > 1 && rates.batch !== null) {
202
+ const saving = (monthlyInputUsd + monthlyOutputUsd) * (1 - rates.batch);
203
+ advisories.push({
204
+ id: 'batch-api',
205
+ severity: 'opportunity',
206
+ ...t.advisories.batchApi(),
207
+ estimatedMonthlyUsd: saving,
208
+ });
209
+ }
210
+ // --- Recommended model ---
211
+ const suggestedTier = recommendTier(optimizedPrompt, tokensAfter);
212
+ /**
213
+ * A model whose capability nobody recorded is never told it is overpowered.
214
+ *
215
+ * Part of the condition rather than an early return, which is how the first
216
+ * version of this was wrong: `return advisories` here skipped every advisory
217
+ * *after* the model check — output-dominated, contradictory instructions —
218
+ * none of which has anything to do with what tier the model is. An unknown
219
+ * capability is a reason to say nothing about capability, not a reason to
220
+ * stop reading the prompt.
221
+ *
222
+ * **Deliberately redundant**, and the one surviving mutant in this change.
223
+ * `TIER_ORDER.unknown` is `-Infinity`, so the comparison is already false and
224
+ * deleting this clause changes no behaviour and no test. It stays because the
225
+ * two express the same rule in different places: an ordering that forgets it
226
+ * and a condition that forgets it both have to happen before a model of
227
+ * unrecorded capability is told it is overpowered.
228
+ */
229
+ if (model.tier !== 'unknown' && TIER_ORDER[suggestedTier] < TIER_ORDER[model.tier]) {
230
+ const candidate = cheapestInTier(suggestedTier, on, pricing, model.provider);
231
+ if (candidate) {
232
+ const candidatePricing = effectivePricing(candidate, on);
233
+ const candidateMonthly = ((tokensAfter / 1_000_000) * candidatePricing.inputPerMTok +
234
+ (usage.avgOutputTokens / 1_000_000) * candidatePricing.outputPerMTok) *
235
+ usage.callsPerMonth *
236
+ (usage.batchEligible ? 0.5 : 1);
237
+ const saving = monthlyInputUsd + monthlyOutputUsd - candidateMonthly;
238
+ if (saving > 0) {
239
+ advisories.push({
240
+ id: 'model-downgrade',
241
+ severity: 'opportunity',
242
+ ...t.advisories.modelDowngrade({
243
+ modelName: model.displayName,
244
+ tier: suggestedTier,
245
+ candidateName: candidate.displayName,
246
+ currentUsd: formatUsd(monthlyInputUsd + monthlyOutputUsd),
247
+ candidateUsd: formatUsd(candidateMonthly),
248
+ }),
249
+ estimatedMonthlyUsd: saving,
250
+ });
251
+ }
252
+ }
253
+ }
254
+ // --- Where the money actually is ---
255
+ if (monthlyOutputUsd > monthlyInputUsd * 2 && usage.avgOutputTokens > 0) {
256
+ advisories.push({
257
+ id: 'output-dominated',
258
+ severity: 'info',
259
+ ...t.advisories.outputDominated({
260
+ outputUsd: formatUsd(monthlyOutputUsd),
261
+ inputUsd: formatUsd(monthlyInputUsd),
262
+ }),
263
+ estimatedMonthlyUsd: null,
264
+ });
265
+ }
266
+ // --- Instructions that fight each other ---
267
+ // Not a saving: a contradiction is a correctness problem that happens to
268
+ // cost tokens, so it carries no dollar figure and sorts on severity instead.
269
+ const contradictions = findContradictions(optimizedPrompt);
270
+ const worst = contradictions[0];
271
+ if (worst) {
272
+ advisories.push({
273
+ id: 'contradictory-instructions',
274
+ severity: 'warning',
275
+ ...t.advisories.contradictoryInstructions({
276
+ axis: t.contradictionAxes[worst.axis],
277
+ firstValue: t.contradictionValues[worst.a.value],
278
+ firstSnippet: worst.a.snippet,
279
+ secondValue: t.contradictionValues[worst.b.value],
280
+ secondSnippet: worst.b.snippet,
281
+ otherCount: contradictions.length - 1,
282
+ }),
283
+ estimatedMonthlyUsd: null,
284
+ });
285
+ }
286
+ // --- Few-shot examples that repeat each other ---
287
+ const examples = analyzeExamples(optimizedPrompt, count);
288
+ if (examples.redundant.length > 0 && examples.redundantTokens > 0) {
289
+ const saving = tokensAfter > 0
290
+ ? monthlyInputUsd * Math.min(1, examples.redundantTokens / tokensAfter)
291
+ : 0;
292
+ const topSimilarity = Math.max(...examples.redundant.map((r) => r.similarity));
293
+ advisories.push({
294
+ id: 'redundant-examples',
295
+ severity: 'opportunity',
296
+ ...t.advisories.redundantExamples({
297
+ redundantCount: examples.redundant.length,
298
+ totalCount: examples.examples.length,
299
+ redundantTokens: examples.redundantTokens,
300
+ topSimilarityPct: Math.round(topSimilarity * 100),
301
+ }),
302
+ estimatedMonthlyUsd: saving > 0 ? saving : null,
303
+ });
304
+ }
305
+ // --- Output format written out twice ---
306
+ const restated = findRestatedFormat(optimizedPrompt, count);
307
+ const movable = findMovableSchema(optimizedPrompt, count);
308
+ if (movable) {
309
+ /**
310
+ * Priced from the tokens, which are real and reproducible, with the
311
+ * uncertainty in the text rather than in the figure.
312
+ *
313
+ * What Trazum knows is how many tokens the block holds; what it cannot know
314
+ * from here is whether the provider accepts a response schema. Withholding
315
+ * the figure for that reason would be the wrong trade — the number is right
316
+ * *if* the move is available, and the advisory says plainly that it does not
317
+ * check. The same posture as `model-downgrade`, which carries a figure and
318
+ * says out loud that it is a keyword heuristic.
319
+ */
320
+ const saving = tokensAfter > 0 ? monthlyInputUsd * Math.min(1, movable.tokens / tokensAfter) : 0;
321
+ advisories.push({
322
+ id: 'movable-output-schema',
323
+ severity: 'opportunity',
324
+ ...t.advisories.movableSchema({
325
+ blocks: movable.blocks,
326
+ tokens: movable.tokens,
327
+ keyList: movable.keys.map((k) => `\`${k}\``).join(', '),
328
+ cue: movable.cue,
329
+ }),
330
+ estimatedMonthlyUsd: saving > 0 ? saving : null,
331
+ });
332
+ }
333
+ if (restated) {
334
+ const saving = tokensAfter > 0
335
+ ? monthlyInputUsd * Math.min(1, restated.restatedTokens / tokensAfter)
336
+ : 0;
337
+ advisories.push({
338
+ id: 'restated-output-format',
339
+ severity: 'opportunity',
340
+ ...t.advisories.restatedOutputFormat({
341
+ restatedCount: restated.restatedKeys.length,
342
+ totalCount: restated.keys.length,
343
+ restatedTokens: restated.restatedTokens,
344
+ keyList: restated.restatedKeys.map((k) => `\`${k}\``).join(', '),
345
+ }),
346
+ estimatedMonthlyUsd: saving > 0 ? saving : null,
347
+ });
348
+ }
349
+ if (promoApplied && model.promo) {
350
+ advisories.push({
351
+ id: 'promo-pricing',
352
+ severity: 'warning',
353
+ ...t.advisories.promoPricing({
354
+ modelName: model.displayName,
355
+ promoInput: model.promo.inputPerMTok,
356
+ promoOutput: model.promo.outputPerMTok,
357
+ until: model.promo.until,
358
+ listInput: model.inputPerMTok,
359
+ listOutput: model.outputPerMTok,
360
+ }),
361
+ estimatedMonthlyUsd: null,
362
+ });
363
+ }
364
+ // Warnings first, then by money. Sorting purely on the dollar figure buried
365
+ // things that are wrong — an overflowing context window, two instructions
366
+ // that contradict each other — underneath a saving of a few dollars, because
367
+ // being wrong carries no price tag.
368
+ const SEVERITY_ORDER = {
369
+ warning: 0,
370
+ opportunity: 1,
371
+ info: 2,
372
+ };
373
+ return advisories.sort((a, b) => SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity] ||
374
+ (b.estimatedMonthlyUsd ?? 0) - (a.estimatedMonthlyUsd ?? 0));
375
+ }
376
+ //# sourceMappingURL=advisories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"advisories.js","sourceRoot":"","sources":["../src/advisories.ts"],"names":[],"mappings":"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"}