@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
@@ -0,0 +1,400 @@
1
+ /**
2
+ * Model and pricing catalogue (USD per million tokens).
3
+ *
4
+ * Source: official Claude API documentation. Prices change: check
5
+ * `PRICING_LAST_REVIEWED` and update this file before making budget decisions.
6
+ * Amazon Bedrock and Vertex AI pricing is set by each partner and is NOT the
7
+ * pricing in this table.
8
+ */
9
+ export const PRICING_LAST_REVIEWED = '2026-06-24';
10
+ /** Cost multipliers relative to the input price. */
11
+ /**
12
+ * The defaults, which are Anthropic's numbers.
13
+ *
14
+ * Still exported and still correct for every Anthropic model, but no longer the
15
+ * whole story: a model can override any of these through `multipliers`, and
16
+ * anything computing a cost should go through `multipliersFor` rather than
17
+ * reading these directly. A cache read is ~10% of input on Anthropic and ~50%
18
+ * on OpenAI, and using one number for both invents a saving.
19
+ */
20
+ export const COST_MULTIPLIERS = {
21
+ /** Cache write with a 5-minute TTL. */
22
+ cacheWrite5m: 1.25,
23
+ /** Cache write with a 1-hour TTL. */
24
+ cacheWrite1h: 2.0,
25
+ /** Cache read: ~10% of the input price. */
26
+ cacheRead: 0.1,
27
+ /** Batch API: 50% discount on input and output. */
28
+ batch: 0.5,
29
+ };
30
+ /**
31
+ * The multipliers that apply to one model, defaults filled in.
32
+ *
33
+ * `batch` stays `null` when the provider has no batch API, which is different
34
+ * from "unspecified": the first should stop the advisory firing, the second
35
+ * should fall back to the default.
36
+ */
37
+ export function multipliersFor(model) {
38
+ const m = model.multipliers ?? {};
39
+ return {
40
+ cacheWrite5m: m.cacheWrite5m ?? COST_MULTIPLIERS.cacheWrite5m,
41
+ cacheWrite1h: m.cacheWrite1h ?? COST_MULTIPLIERS.cacheWrite1h,
42
+ cacheRead: m.cacheRead ?? COST_MULTIPLIERS.cacheRead,
43
+ batch: m.batch === undefined ? COST_MULTIPLIERS.batch : m.batch,
44
+ };
45
+ }
46
+ export const MODELS = [
47
+ {
48
+ id: 'claude-fable-5',
49
+ provider: 'anthropic',
50
+ displayName: 'Claude Fable 5',
51
+ inputPerMTok: 10,
52
+ outputPerMTok: 50,
53
+ contextWindow: 1_000_000,
54
+ cacheMinTokens: 512,
55
+ capability: 'frontier',
56
+ tier: 'frontier',
57
+ notes: 'Highest capability. Requires 30-day data retention.',
58
+ },
59
+ {
60
+ id: 'claude-mythos-5',
61
+ provider: 'anthropic',
62
+ displayName: 'Claude Mythos 5',
63
+ recommendable: false,
64
+ inputPerMTok: 10,
65
+ outputPerMTok: 50,
66
+ contextWindow: 1_000_000,
67
+ cacheMinTokens: 512,
68
+ capability: 'frontier',
69
+ tier: 'frontier',
70
+ notes: 'Available only through Project Glasswing.',
71
+ },
72
+ {
73
+ id: 'claude-opus-5',
74
+ provider: 'anthropic',
75
+ displayName: 'Claude Opus 5',
76
+ inputPerMTok: 5,
77
+ outputPerMTok: 25,
78
+ contextWindow: 1_000_000,
79
+ cacheMinTokens: 512,
80
+ capability: 'large',
81
+ tier: 'opus',
82
+ notes: '512-token cache minimum: caches prompts that would miss on Opus 4.6.',
83
+ },
84
+ {
85
+ id: 'claude-opus-4-8',
86
+ provider: 'anthropic',
87
+ displayName: 'Claude Opus 4.8',
88
+ inputPerMTok: 5,
89
+ outputPerMTok: 25,
90
+ contextWindow: 1_000_000,
91
+ cacheMinTokens: 1024,
92
+ capability: 'large',
93
+ tier: 'opus',
94
+ },
95
+ {
96
+ id: 'claude-opus-4-7',
97
+ provider: 'anthropic',
98
+ displayName: 'Claude Opus 4.7',
99
+ inputPerMTok: 5,
100
+ outputPerMTok: 25,
101
+ contextWindow: 1_000_000,
102
+ cacheMinTokens: 2048,
103
+ capability: 'large',
104
+ tier: 'opus',
105
+ },
106
+ {
107
+ id: 'claude-opus-4-6',
108
+ provider: 'anthropic',
109
+ displayName: 'Claude Opus 4.6',
110
+ inputPerMTok: 5,
111
+ outputPerMTok: 25,
112
+ contextWindow: 1_000_000,
113
+ cacheMinTokens: 4096,
114
+ capability: 'large',
115
+ tier: 'opus',
116
+ },
117
+ {
118
+ id: 'claude-sonnet-5',
119
+ provider: 'anthropic',
120
+ displayName: 'Claude Sonnet 5',
121
+ inputPerMTok: 3,
122
+ outputPerMTok: 15,
123
+ contextWindow: 1_000_000,
124
+ cacheMinTokens: 1024,
125
+ capability: 'mid',
126
+ tier: 'sonnet',
127
+ promo: { inputPerMTok: 2, outputPerMTok: 10, until: '2026-08-31' },
128
+ notes: 'Introductory pricing of 2/10 until 2026-08-31; 3/15 afterwards.',
129
+ },
130
+ {
131
+ id: 'claude-sonnet-4-6',
132
+ provider: 'anthropic',
133
+ displayName: 'Claude Sonnet 4.6',
134
+ inputPerMTok: 3,
135
+ outputPerMTok: 15,
136
+ contextWindow: 1_000_000,
137
+ cacheMinTokens: 1024,
138
+ capability: 'mid',
139
+ tier: 'sonnet',
140
+ },
141
+ {
142
+ id: 'claude-haiku-4-5',
143
+ provider: 'anthropic',
144
+ displayName: 'Claude Haiku 4.5',
145
+ inputPerMTok: 1,
146
+ outputPerMTok: 5,
147
+ contextWindow: 200_000,
148
+ cacheMinTokens: 4096,
149
+ capability: 'small',
150
+ tier: 'haiku',
151
+ notes: '200K context window and a 64K output cap, smaller than the rest.',
152
+ },
153
+ // ------------------------------------------------------------------------
154
+ // OpenAI
155
+ //
156
+ // Caching is AUTOMATIC above 1,024 tokens — there is no cache_control to set,
157
+ // and a cached read costs about half the input price rather than a tenth. The
158
+ // reordering advice is identical (a prefix is a prefix), the marker advice is
159
+ // not, which is why `caching` and `multipliers` exist at all.
160
+ // ------------------------------------------------------------------------
161
+ {
162
+ id: 'gpt-5',
163
+ provider: 'openai',
164
+ displayName: 'GPT-5',
165
+ inputPerMTok: 1.25,
166
+ outputPerMTok: 10,
167
+ contextWindow: 400_000,
168
+ cacheMinTokens: 1024,
169
+ caching: 'automatic',
170
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
171
+ capability: 'frontier',
172
+ tier: 'frontier',
173
+ notes: 'Caching is automatic above 1,024 tokens; there is no cache_control to set.',
174
+ },
175
+ {
176
+ id: 'gpt-5-mini',
177
+ provider: 'openai',
178
+ displayName: 'GPT-5 mini',
179
+ inputPerMTok: 0.25,
180
+ outputPerMTok: 2,
181
+ contextWindow: 400_000,
182
+ cacheMinTokens: 1024,
183
+ caching: 'automatic',
184
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
185
+ capability: 'mid',
186
+ tier: 'sonnet',
187
+ },
188
+ {
189
+ id: 'gpt-5-nano',
190
+ provider: 'openai',
191
+ displayName: 'GPT-5 nano',
192
+ inputPerMTok: 0.05,
193
+ outputPerMTok: 0.4,
194
+ contextWindow: 400_000,
195
+ cacheMinTokens: 1024,
196
+ caching: 'automatic',
197
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
198
+ capability: 'small',
199
+ tier: 'haiku',
200
+ },
201
+ // ------------------------------------------------------------------------
202
+ // Google
203
+ // ------------------------------------------------------------------------
204
+ {
205
+ id: 'gemini-2.5-pro',
206
+ provider: 'google',
207
+ displayName: 'Gemini 2.5 Pro',
208
+ inputPerMTok: 1.25,
209
+ outputPerMTok: 10,
210
+ contextWindow: 1_048_576,
211
+ cacheMinTokens: 2048,
212
+ caching: 'explicit',
213
+ multipliers: { cacheRead: 0.25, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
214
+ capability: 'large',
215
+ tier: 'opus',
216
+ notes: 'Context caching is billed for storage per hour as well as per read.',
217
+ },
218
+ {
219
+ id: 'gemini-2.5-flash',
220
+ provider: 'google',
221
+ displayName: 'Gemini 2.5 Flash',
222
+ inputPerMTok: 0.3,
223
+ outputPerMTok: 2.5,
224
+ contextWindow: 1_048_576,
225
+ cacheMinTokens: 1024,
226
+ caching: 'explicit',
227
+ multipliers: { cacheRead: 0.25, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
228
+ capability: 'mid',
229
+ tier: 'sonnet',
230
+ },
231
+ // ------------------------------------------------------------------------
232
+ // Moonshot
233
+ // ------------------------------------------------------------------------
234
+ {
235
+ id: 'kimi-k2',
236
+ provider: 'moonshot',
237
+ displayName: 'Kimi K2',
238
+ inputPerMTok: 0.6,
239
+ outputPerMTok: 2.5,
240
+ contextWindow: 256_000,
241
+ cacheMinTokens: 1024,
242
+ caching: 'automatic',
243
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
244
+ capability: 'mid',
245
+ tier: 'sonnet',
246
+ notes: 'No batch API: the batch advisory stays quiet rather than offering a discount you cannot buy.',
247
+ },
248
+ // ------------------------------------------------------------------------
249
+ // DeepSeek
250
+ // ------------------------------------------------------------------------
251
+ {
252
+ id: 'deepseek-v3',
253
+ provider: 'deepseek',
254
+ displayName: 'DeepSeek V3',
255
+ inputPerMTok: 0.27,
256
+ outputPerMTok: 1.1,
257
+ contextWindow: 128_000,
258
+ cacheMinTokens: 1024,
259
+ caching: 'automatic',
260
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
261
+ capability: 'mid',
262
+ tier: 'sonnet',
263
+ },
264
+ // ------------------------------------------------------------------------
265
+ // xAI
266
+ // ------------------------------------------------------------------------
267
+ {
268
+ id: 'grok-4',
269
+ provider: 'xai',
270
+ displayName: 'Grok 4',
271
+ inputPerMTok: 3,
272
+ outputPerMTok: 15,
273
+ contextWindow: 256_000,
274
+ cacheMinTokens: 1024,
275
+ caching: 'automatic',
276
+ multipliers: { cacheRead: 0.25, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
277
+ capability: 'large',
278
+ tier: 'opus',
279
+ },
280
+ // ------------------------------------------------------------------------
281
+ // Mistral
282
+ // ------------------------------------------------------------------------
283
+ {
284
+ id: 'mistral-large-2',
285
+ provider: 'mistral',
286
+ displayName: 'Mistral Large 2',
287
+ inputPerMTok: 2,
288
+ outputPerMTok: 6,
289
+ contextWindow: 128_000,
290
+ cacheMinTokens: 0,
291
+ caching: 'none',
292
+ multipliers: { batch: 0.5 },
293
+ capability: 'large',
294
+ tier: 'opus',
295
+ notes: 'No prompt caching: reordering still helps readability but saves nothing here.',
296
+ },
297
+ ];
298
+ export const DEFAULT_MODEL = 'claude-opus-5';
299
+ function makeCatalogue(models, lastReviewed, overriddenModels = [], addedModels = []) {
300
+ return {
301
+ models,
302
+ byId: new Map(models.map((m) => [m.id, m])),
303
+ lastReviewed,
304
+ overriddenModels,
305
+ addedModels,
306
+ };
307
+ }
308
+ /** The prices compiled into this release. */
309
+ export const BUNDLED_CATALOGUE = makeCatalogue(MODELS, PRICING_LAST_REVIEWED);
310
+ /** Looks a model up in a specific catalogue. */
311
+ export function modelFrom(catalogue, id) {
312
+ const model = catalogue.byId.get(id);
313
+ if (!model) {
314
+ throw new Error(`Unknown model: "${id}". Available: ${catalogue.models.map((m) => m.id).join(', ')}`);
315
+ }
316
+ return model;
317
+ }
318
+ /**
319
+ * Cheapest model of a tier within a catalogue.
320
+ *
321
+ * Compared on the **effective** price, so a model in a promotional window is
322
+ * ranked at what it actually costs today rather than at its list price.
323
+ */
324
+ export function cheapestOfTierIn(catalogue, tier, on = new Date(),
325
+ /**
326
+ * Restrict to one provider. Omit to search the whole catalogue, which is what
327
+ * this did before other providers existed — kept as the default so the
328
+ * signature stays additive, though the advisory always passes one: switching
329
+ * vendor is a migration, not a cheaper model.
330
+ */
331
+ provider) {
332
+ const candidates = catalogue.models.filter((m) => m.tier === tier &&
333
+ m.recommendable !== false &&
334
+ (provider === undefined || m.provider === provider));
335
+ const first = candidates[0];
336
+ if (!first)
337
+ throw new Error(`No models for tier "${tier}"`);
338
+ return candidates.reduce((best, m) => effectivePricing(m, on).inputPerMTok < effectivePricing(best, on).inputPerMTok ? m : best, first);
339
+ }
340
+ export function getModel(id) {
341
+ return modelFrom(BUNDLED_CATALOGUE, id);
342
+ }
343
+ export function listModels() {
344
+ return [...MODELS];
345
+ }
346
+ /** Effective price on a given date, applying any live promotion. */
347
+ export function effectivePricing(model, on = new Date()) {
348
+ if (model.promo) {
349
+ const until = new Date(`${model.promo.until}T23:59:59.999Z`);
350
+ if (on.getTime() <= until.getTime()) {
351
+ return {
352
+ inputPerMTok: model.promo.inputPerMTok,
353
+ outputPerMTok: model.promo.outputPerMTok,
354
+ promoApplied: true,
355
+ };
356
+ }
357
+ }
358
+ return {
359
+ inputPerMTok: model.inputPerMTok,
360
+ outputPerMTok: model.outputPerMTok,
361
+ promoApplied: false,
362
+ };
363
+ }
364
+ /** Cheapest model of each capability tier, for recommendations. */
365
+ export function cheapestOfTier(tier) {
366
+ const candidates = MODELS.filter((m) => m.tier === tier && m.recommendable !== false);
367
+ const first = candidates[0];
368
+ if (!first)
369
+ throw new Error(`No models for tier "${tier}"`);
370
+ return candidates.reduce((best, m) => (m.inputPerMTok < best.inputPerMTok ? m : best), first);
371
+ }
372
+ /**
373
+ * Whole days between a `YYYY-MM-DD` review date and now, or `null`.
374
+ *
375
+ * Every dollar figure Trazum prints descends from a price list, and the list
376
+ * carries the date it was checked. Printing only that date makes the reader do
377
+ * arithmetic against today to learn the one thing they wanted to know — whether
378
+ * to trust it — and a reader who is not already suspicious will not bother.
379
+ *
380
+ * `now` is a parameter for the reason `computeSavings` takes a `Date`: a function
381
+ * that reads the clock can only be asserted for shape.
382
+ *
383
+ * Compared at UTC midnight on both sides, so the answer does not change by one
384
+ * depending on what time of day the command runs. `null` for anything that is not
385
+ * a date — an overlay supplies this string, and a wrong one should read as
386
+ * unknown rather than as a confident number computed from `NaN`.
387
+ */
388
+ export function reviewAgeDays(lastReviewed, now) {
389
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(lastReviewed))
390
+ return null;
391
+ const then = Date.parse(`${lastReviewed}T00:00:00Z`);
392
+ if (Number.isNaN(then))
393
+ return null;
394
+ const today = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
395
+ const days = Math.round((today - then) / 86_400_000);
396
+ // A future date is a typo or a clock problem, not an age. Reported as unknown
397
+ // rather than as a negative number of days, which reads like a bug either way.
398
+ return days < 0 ? null : days;
399
+ }
400
+ //# sourceMappingURL=pricing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricing.js","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import type { TokenCounter } from './types.js';
2
+ /**
3
+ * What a prompt is made of, so a team can decide which one to fix first.
4
+ *
5
+ * ## Why this is not a score
6
+ *
7
+ * The obvious shape for "which prompts should we optimise?" is a number out of
8
+ * a hundred, and it is the wrong shape. `Complexity: 74` cannot be argued with,
9
+ * cannot be reproduced by hand, and does not tell anybody what to do on Monday.
10
+ * Worse, the weights that turn four measurements into one number are invented —
11
+ * and once invented they get tuned until the ranking looks right, which is
12
+ * fitting the metric to the answer.
13
+ *
14
+ * So this returns **the measurements**, each with a definition you can check
15
+ * against the prompt in front of you, and ranks on the one quantity that is not
16
+ * a matter of opinion: **what optimising it would actually save.** That figure
17
+ * comes from running the deterministic rules, not from a formula.
18
+ *
19
+ * The structural facts are the *explanation* for a prompt's position in that
20
+ * ranking, not a substitute for it. "1,204 tokens across 8 sentences" says why
21
+ * a prompt is worth looking at; "$310 a month recoverable" says whether it is
22
+ * worth looking at before the other thirty-nine.
23
+ *
24
+ * ## What each measurement means, exactly
25
+ *
26
+ * - **sentences** — spans ending in `.`, `!`, `?`, `。`, `!`, `?`, or a line
27
+ * break where the line ends without punctuation (a bullet is a sentence).
28
+ * Protected content is excluded, so a code block is not forty sentences.
29
+ * - **tokensPerSentence** — the verbosity signal, and the only ratio here. It
30
+ * is length-independent: a padded 300-token prompt and a padded 3,000-token
31
+ * one look the same, which is the point. Reported with units, never as an
32
+ * index.
33
+ * - **examples / exampleTokens** — from `findExamples`, the same detector the
34
+ * advisories use. Few-shot examples are usually the largest single block in
35
+ * an expensive prompt and the easiest to trim by one.
36
+ * - **formatTokens** — a restated output format (a JSON schema written out
37
+ * twice, or written out at all when the API takes a schema parameter).
38
+ * - **protectedTokens** — code, URLs, placeholders and tags. Counted separately
39
+ * because **no amount of optimising will touch them**, and a prompt that is
40
+ * 80% code has far less headroom than its size suggests. Leaving this out is
41
+ * how a ranking sends somebody to spend an afternoon on a file that cannot
42
+ * move.
43
+ */
44
+ export interface PromptProfile {
45
+ tokens: number;
46
+ /** Tokens that cannot be touched: code, URLs, placeholders, tags. */
47
+ protectedTokens: number;
48
+ sentences: number;
49
+ /** `tokens / sentences`, rounded to one decimal. Zero when there are none. */
50
+ tokensPerSentence: number;
51
+ examples: number;
52
+ exampleTokens: number;
53
+ /** Tokens in a restated output format, or 0. */
54
+ formatTokens: number;
55
+ }
56
+ /**
57
+ * Sentences in the mutable part of the prompt.
58
+ *
59
+ * Deliberately simple and stated rather than clever. An abbreviation splits a
60
+ * sentence in two here, and that is accepted: the number is used as a
61
+ * denominator for a verbosity ratio, where a few percent of noise changes
62
+ * nothing, and the alternative is a sentence tokeniser this project would then
63
+ * have to defend.
64
+ */
65
+ export declare function countSentences(prompt: string): number;
66
+ export interface ProfileOptions {
67
+ count?: TokenCounter;
68
+ }
69
+ /** Measures a prompt. Deterministic, offline, and free. */
70
+ export declare function profilePrompt(prompt: string, options?: ProfileOptions): PromptProfile;
71
+ //# sourceMappingURL=profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,YAAY,EAAE,MAAM,CAAC;CACtB;AAID;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAuBrD;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAED,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,aAAa,CAqBzF"}
@@ -0,0 +1,55 @@
1
+ import { segment } from './segment.js';
2
+ import { findExamples, findRestatedFormat } from './structure.js';
3
+ import { estimateTokens } from './tokenizer.js';
4
+ const SENTENCE_END = /[.!?。!?]+/;
5
+ /**
6
+ * Sentences in the mutable part of the prompt.
7
+ *
8
+ * Deliberately simple and stated rather than clever. An abbreviation splits a
9
+ * sentence in two here, and that is accepted: the number is used as a
10
+ * denominator for a verbosity ratio, where a few percent of noise changes
11
+ * nothing, and the alternative is a sentence tokeniser this project would then
12
+ * have to defend.
13
+ */
14
+ export function countSentences(prompt) {
15
+ let total = 0;
16
+ for (const piece of segment(prompt)) {
17
+ if (piece.kind === 'protected')
18
+ continue;
19
+ for (const line of piece.text.split('\n')) {
20
+ const trimmed = line.trim();
21
+ if (trimmed === '')
22
+ continue;
23
+ const parts = trimmed
24
+ .split(SENTENCE_END)
25
+ .map((part) => part.trim())
26
+ .filter((part) => part !== '');
27
+ // A line with no terminal punctuation is still one thing being said — a
28
+ // bullet, a heading, a rule in a list. Counting it as zero would make
29
+ // every bulleted prompt look infinitely verbose.
30
+ total += Math.max(1, parts.length);
31
+ }
32
+ }
33
+ return total;
34
+ }
35
+ /** Measures a prompt. Deterministic, offline, and free. */
36
+ export function profilePrompt(prompt, options = {}) {
37
+ const count = options.count ?? estimateTokens;
38
+ const tokens = count(prompt);
39
+ const protectedTokens = segment(prompt)
40
+ .filter((piece) => piece.kind === 'protected')
41
+ .reduce((sum, piece) => sum + count(piece.text), 0);
42
+ const sentences = countSentences(prompt);
43
+ const examples = findExamples(prompt, count);
44
+ const format = findRestatedFormat(prompt, count);
45
+ return {
46
+ tokens,
47
+ protectedTokens,
48
+ sentences,
49
+ tokensPerSentence: sentences === 0 ? 0 : Math.round((tokens / sentences) * 10) / 10,
50
+ examples: examples.length,
51
+ exampleTokens: examples.reduce((sum, block) => sum + block.tokens, 0),
52
+ formatTokens: format?.restatedTokens ?? 0,
53
+ };
54
+ }
55
+ //# sourceMappingURL=profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.js","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AA2DhD,MAAM,YAAY,GAAG,WAAW,CAAC;AAEjC;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACpC,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW;YAAE,SAAS;QAEzC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,OAAO,KAAK,EAAE;gBAAE,SAAS;YAE7B,MAAM,KAAK,GAAG,OAAO;iBAClB,KAAK,CAAC,YAAY,CAAC;iBACnB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;iBAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC;YAEjC,wEAAwE;YACxE,sEAAsE;YACtE,iDAAiD;YACjD,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAMD,2DAA2D;AAC3D,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,OAAO,GAAmB,EAAE;IACxE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,cAAc,CAAC;IAE9C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,MAAM,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC;SACpC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,WAAW,CAAC;SAC7C,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAEtD,MAAM,SAAS,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,kBAAkB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAEjD,OAAO;QACL,MAAM;QACN,eAAe;QACf,SAAS;QACT,iBAAiB,EAAE,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE;QACnF,QAAQ,EAAE,QAAQ,CAAC,MAAM;QACzB,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QACrE,YAAY,EAAE,MAAM,EAAE,cAAc,IAAI,CAAC;KAC1C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Exports a before/after pair as a suite somebody else's harness can run.
3
+ *
4
+ * `trazum eval` answers "does the model still say the same thing?" — semantic
5
+ * agreement, measured against the model's own variance. That is the question
6
+ * Trazum is qualified to ask, and it is not the question a team actually needs
7
+ * answered before shipping. *Their* question is whether the classifier still
8
+ * hits 94%, whether the JSON still parses, whether the refusal rate moved. Those
9
+ * are assertions about their task, and Trazum has no business inventing them.
10
+ *
11
+ * So this hands over the part it *can* build correctly — a suite in which the
12
+ * only variable is the prompt, with both versions and every case already wired
13
+ * — and leaves the assertions where they belong.
14
+ *
15
+ * ## Why JSON rather than YAML
16
+ *
17
+ * promptfoo reads `promptfooconfig.json` as readily as the YAML. This package
18
+ * has no dependencies and is not going to acquire a YAML emitter, and a
19
+ * hand-rolled one would be a quoting bug waiting for the first prompt
20
+ * containing a colon, a tab, or a line ending in a space. `JSON.stringify`
21
+ * escapes everything correctly, and JSON is a subset of YAML, so the file can
22
+ * be renamed if somebody prefers.
23
+ */
24
+ export interface PromptfooExport {
25
+ /** The config, ready for `JSON.stringify`. */
26
+ config: Record<string, unknown>;
27
+ /** Things the reader has to know before trusting the run. */
28
+ warnings: PromptfooWarning[];
29
+ }
30
+ export type PromptfooWarning =
31
+ /** `${x}` or `{x}`: promptfoo substitutes `{{x}}` and will leave these alone. */
32
+ {
33
+ kind: 'unsupported-placeholder';
34
+ detail: string;
35
+ }
36
+ /** More than one distinct placeholder, and one value per case to fill them. */
37
+ | {
38
+ kind: 'multiple-placeholders';
39
+ detail: string;
40
+ }
41
+ /** The provider has no promptfoo id here, so the model string is a guess. */
42
+ | {
43
+ kind: 'unmapped-provider';
44
+ detail: string;
45
+ }
46
+ /** No placeholder at all: the case is appended, as `trazum eval` does. */
47
+ | {
48
+ kind: 'appended-input';
49
+ detail: string;
50
+ };
51
+ export interface PromptfooOptions {
52
+ /** Model id from Trazum's catalogue, mapped to a promptfoo provider. */
53
+ model?: string;
54
+ /** Rule level used to produce the optimised prompt, for the label. */
55
+ level?: string;
56
+ }
57
+ export declare function toPromptfoo(original: string, optimized: string, cases: readonly string[], options?: PromptfooOptions): PromptfooExport;
58
+ //# sourceMappingURL=promptfoo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promptfoo.d.ts","sourceRoot":"","sources":["../src/promptfoo.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,6DAA6D;IAC7D,QAAQ,EAAE,gBAAgB,EAAE,CAAC;CAC9B;AAED,MAAM,MAAM,gBAAgB;AAC1B,iFAAiF;AAC/E;IAAE,IAAI,EAAE,yBAAyB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACrD,+EAA+E;GAC7E;IAAE,IAAI,EAAE,uBAAuB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACnD,6EAA6E;GAC3E;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAC/C,0EAA0E;GACxE;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/C,MAAM,WAAW,gBAAgB;IAC/B,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA4ED,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,GAAE,gBAAqB,GAC7B,eAAe,CAkFjB"}