@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,183 @@
1
+ import { buildAdvisories } from './advisories.js';
2
+ import { extractChanges } from './changes.js';
3
+ import { DEFAULT_LOCALE, getMessages } from './i18n/index.js';
4
+ import { BUNDLED_CATALOGUE, DEFAULT_MODEL } from './pricing.js';
5
+ import { RULES } from './rules.js';
6
+ import { computeSavings } from './savings.js';
7
+ import { join, segment } from './segment.js';
8
+ import { estimateTokens } from './tokenizer.js';
9
+ const MASK_OPEN = '\uE000';
10
+ const MASK_CLOSE = '\uE001';
11
+ const MASK_BASE = 0xe100;
12
+ /** Private-use-area slots available for masks. */
13
+ const MASK_CAPACITY = 0xf8ff - MASK_BASE;
14
+ export const DEFAULT_USAGE = {
15
+ model: DEFAULT_MODEL,
16
+ callsPerMonth: 1000,
17
+ avgOutputTokens: 500,
18
+ cacheHitRate: 0.9,
19
+ batchEligible: false,
20
+ };
21
+ /**
22
+ * Replaces every protected segment with a 3-character marker from the Unicode
23
+ * private use area. The rules work on the masked text, so none of them can
24
+ * touch code, URLs or template placeholders.
25
+ */
26
+ function mask(segments) {
27
+ const vault = [];
28
+ let text = '';
29
+ for (const seg of segments) {
30
+ if (seg.kind === 'mutable') {
31
+ text += seg.text;
32
+ continue;
33
+ }
34
+ if (vault.length >= MASK_CAPACITY) {
35
+ // A prompt with thousands of protected blocks is left as-is rather than
36
+ // risking a mask collision.
37
+ text += seg.text;
38
+ continue;
39
+ }
40
+ const slot = String.fromCharCode(MASK_BASE + vault.length);
41
+ vault.push(seg.text);
42
+ text += MASK_OPEN + slot + MASK_CLOSE;
43
+ }
44
+ return { text, vault };
45
+ }
46
+ /** Puts the protected content back. */
47
+ function unmask(text, vault) {
48
+ return text.replace(new RegExp(`${MASK_OPEN}([\\s\\S])${MASK_CLOSE}`, 'g'), (whole, slot) => {
49
+ const index = slot.charCodeAt(0) - MASK_BASE;
50
+ const original = vault[index];
51
+ return original === undefined ? whole : original;
52
+ });
53
+ }
54
+ /** Distinct protected texts that must still be present after optimising. */
55
+ function distinctProtected(segments) {
56
+ return [...new Set(segments.filter((s) => s.kind === 'protected').map((s) => s.text))];
57
+ }
58
+ /**
59
+ * Optimises a prompt by applying deterministic rules.
60
+ *
61
+ * The result is reproducible: the same input always yields the same output and
62
+ * running it costs nothing. The optional LLM pass lives separately, in
63
+ * `refineWithLlm`.
64
+ */
65
+ export function optimize(prompt, options = {}) {
66
+ const level = options.level ?? 'safe';
67
+ const locale = options.locale ?? DEFAULT_LOCALE;
68
+ const t = getMessages(locale);
69
+ const disabled = new Set(options.disableRules ?? []);
70
+ const count = options.tokenCounter ?? estimateTokens;
71
+ const segments = segment(prompt);
72
+ const mustSurvive = distinctProtected(segments);
73
+ const { text: maskedOriginal, vault } = mask(segments);
74
+ let current = maskedOriginal;
75
+ const ruleResults = [];
76
+ for (const rule of RULES) {
77
+ if (disabled.has(rule.id))
78
+ continue;
79
+ if (rule.level === 'aggressive' && level !== 'aggressive')
80
+ continue;
81
+ const currentUnmasked = unmask(current, vault);
82
+ const before = count(currentUnmasked);
83
+ const { text: candidate, hits } = rule.apply(current);
84
+ if (hits === 0 || candidate === current)
85
+ continue;
86
+ const candidateUnmasked = unmask(candidate, vault);
87
+ // Safety net: if a rule made protected content disappear, that rule is
88
+ // dropped rather than returning a broken prompt.
89
+ const lostProtected = mustSurvive.some((text) => !candidateUnmasked.includes(text));
90
+ if (lostProtected)
91
+ continue;
92
+ const after = count(candidateUnmasked);
93
+ // Captured against the unmasked text on both sides, so the snippets read
94
+ // as the author wrote them rather than showing private-use markers.
95
+ const changes = extractChanges(currentUnmasked, candidateUnmasked);
96
+ current = candidate;
97
+ const copy = t.rules[rule.id];
98
+ ruleResults.push({
99
+ id: rule.id,
100
+ title: copy.title,
101
+ rationale: copy.rationale,
102
+ level: rule.level,
103
+ hits,
104
+ tokensSaved: Math.max(0, before - after),
105
+ changes,
106
+ });
107
+ }
108
+ const optimized = unmask(current, vault).trim();
109
+ const tokensBefore = count(prompt);
110
+ const tokensAfter = count(optimized);
111
+ const usage = { ...DEFAULT_USAGE, ...options.usage };
112
+ const pricing = options.pricing ?? BUNDLED_CATALOGUE;
113
+ const savings = computeSavings(tokensBefore, tokensAfter, usage, new Date(), pricing);
114
+ const advisories = buildAdvisories(optimized, tokensAfter, usage, { count, locale, pricing });
115
+ return {
116
+ original: prompt,
117
+ optimized,
118
+ tokensBefore,
119
+ tokensAfter,
120
+ tokensSaved: tokensBefore - tokensAfter,
121
+ reductionPct: tokensBefore > 0 ? ((tokensBefore - tokensAfter) / tokensBefore) * 100 : 0,
122
+ rules: ruleResults,
123
+ advisories,
124
+ savings,
125
+ usage,
126
+ locale,
127
+ tokenSource: options.tokenCounter ? 'external' : 'heuristic',
128
+ pricingSource: {
129
+ lastReviewed: pricing.lastReviewed,
130
+ overriddenModels: pricing.overriddenModels,
131
+ addedModels: pricing.addedModels,
132
+ },
133
+ };
134
+ }
135
+ /**
136
+ * Recomputes the report with an exact token counter (e.g. the official
137
+ * token-counting endpoint).
138
+ *
139
+ * Only the headline numbers and the saving are recomputed. Per-rule token
140
+ * attribution stays on the heuristic estimate: asking a remote counter once
141
+ * per rule would multiply the calls without changing any decision.
142
+ */
143
+ export async function withExactTokenCounts(result, counter, pricing = BUNDLED_CATALOGUE) {
144
+ // A result priced against an overlay cannot be recomputed against the bundled
145
+ // catalogue: the token counts would come from one source and the money from
146
+ // another, and the report would disagree with itself with nothing to show why.
147
+ // Throwing is the only honest option — silently reverting to bundled prices is
148
+ // the exact failure the overlay exists to prevent.
149
+ const overlaid = result.pricingSource.overriddenModels.length > 0 ||
150
+ result.pricingSource.addedModels.length > 0;
151
+ if (overlaid && pricing === BUNDLED_CATALOGUE) {
152
+ throw new Error('This result was priced against a pricing overlay, so withExactTokenCounts ' +
153
+ 'needs the same catalogue passed as its third argument. Recomputing against ' +
154
+ 'the bundled prices would make the token counts and the costs disagree.');
155
+ }
156
+ const [tokensBefore, tokensAfter] = await Promise.all([
157
+ counter(result.original),
158
+ counter(result.optimized),
159
+ ]);
160
+ const savings = computeSavings(tokensBefore, tokensAfter, result.usage, new Date(), pricing);
161
+ const advisories = buildAdvisories(result.optimized, tokensAfter, result.usage, {
162
+ locale: result.locale,
163
+ pricing,
164
+ });
165
+ return {
166
+ ...result,
167
+ tokensBefore,
168
+ tokensAfter,
169
+ tokensSaved: tokensBefore - tokensAfter,
170
+ reductionPct: tokensBefore > 0 ? ((tokensBefore - tokensAfter) / tokensBefore) * 100 : 0,
171
+ savings,
172
+ advisories,
173
+ tokenSource: 'external',
174
+ pricingSource: {
175
+ lastReviewed: pricing.lastReviewed,
176
+ overriddenModels: pricing.overriddenModels,
177
+ addedModels: pricing.addedModels,
178
+ },
179
+ };
180
+ }
181
+ /** Re-exports of internals useful to anyone extending the library. */
182
+ export { join, mask as maskProtected, segment, unmask as unmaskProtected };
183
+ //# sourceMappingURL=optimize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optimize.js","sourceRoot":"","sources":["../src/optimize.ts"],"names":[],"mappings":"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"}
package/dist/otlp.d.ts ADDED
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Prompt cost as OpenTelemetry metrics, in the OTLP/HTTP JSON encoding.
3
+ *
4
+ * **Trazum writes the payload; it does not send it.** That is a decision, not a
5
+ * missing feature. Pushing to a collector would mean holding an endpoint and a
6
+ * credential, and this repository has twice shipped an SSRF where a URL reached
7
+ * `fetch` without being the URL that was checked. A command that writes a file
8
+ * has no such failure mode, and `curl --data-binary @metrics.json $OTLP_ENDPOINT`
9
+ * is one line in the pipeline that already has the credential.
10
+ *
11
+ * No dependency on `@opentelemetry/*` either — the JSON encoding is a stable,
12
+ * documented wire format, and this package has no runtime dependencies by design.
13
+ *
14
+ * The two encoding rules that are easy to get wrong and silently produce a
15
+ * payload a collector drops:
16
+ *
17
+ * - **64-bit integers are strings.** `timeUnixNano` and `asInt` are `int64` in
18
+ * the protobuf, and JSON numbers cannot hold one exactly, so the JSON mapping
19
+ * specifies strings. A collector reading `1786000000000000000` as a double
20
+ * loses the last digits of every timestamp.
21
+ * - **A gauge is `asDouble` or `asInt`, never both**, and a money figure is a
22
+ * double. Sending `asInt` for `$41.20` silently reports `41`.
23
+ */
24
+ /** Attributes as OTLP encodes them: a list of key/value, not an object. */
25
+ export interface OtlpAttribute {
26
+ key: string;
27
+ value: {
28
+ stringValue: string;
29
+ } | {
30
+ intValue: string;
31
+ } | {
32
+ doubleValue: number;
33
+ };
34
+ }
35
+ export interface OtlpDataPoint {
36
+ attributes: OtlpAttribute[];
37
+ timeUnixNano: string;
38
+ asInt?: string;
39
+ asDouble?: number;
40
+ }
41
+ export interface OtlpMetric {
42
+ name: string;
43
+ description: string;
44
+ unit: string;
45
+ gauge: {
46
+ dataPoints: OtlpDataPoint[];
47
+ };
48
+ }
49
+ export interface OtlpPayload {
50
+ resourceMetrics: [
51
+ {
52
+ resource: {
53
+ attributes: OtlpAttribute[];
54
+ };
55
+ scopeMetrics: [{
56
+ scope: {
57
+ name: string;
58
+ version: string;
59
+ };
60
+ metrics: OtlpMetric[];
61
+ }];
62
+ }
63
+ ];
64
+ }
65
+ /** What `doctor` knows, in the shape this module needs. */
66
+ export interface OtlpInput {
67
+ /** Every prompt surveyed, with the count a budget would be compared against. */
68
+ prompts: readonly {
69
+ path: string;
70
+ tokens: number;
71
+ overBudget: boolean;
72
+ budgeted: boolean;
73
+ }[];
74
+ /** Advisories rolled up by id, as the survey reports them. */
75
+ findings: readonly {
76
+ id: string;
77
+ prompts: number;
78
+ monthlyUsd: number | null;
79
+ }[];
80
+ model: string;
81
+ callsPerMonth: number;
82
+ }
83
+ /**
84
+ * `atMillis` is a parameter rather than a call to `Date.now()`.
85
+ *
86
+ * The same reason `computeSavings` takes a `Date`: a function that reads the
87
+ * clock cannot be asserted against a fixed expectation, and every test of it
88
+ * ends up asserting only its shape. Here the timestamp *is* half the payload.
89
+ */
90
+ export declare function toOtlpMetrics(input: OtlpInput, atMillis: number): OtlpPayload;
91
+ //# sourceMappingURL=otlp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otlp.d.ts","sourceRoot":"","sources":["../src/otlp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;CACjF;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE;QAAE,UAAU,EAAE,aAAa,EAAE,CAAA;KAAE,CAAC;CACxC;AAED,MAAM,WAAW,WAAW;IAC1B,eAAe,EAAE;QACf;YACE,QAAQ,EAAE;gBAAE,UAAU,EAAE,aAAa,EAAE,CAAA;aAAE,CAAC;YAC1C,YAAY,EAAE,CAAC;gBAAE,KAAK,EAAE;oBAAE,IAAI,EAAE,MAAM,CAAC;oBAAC,OAAO,EAAE,MAAM,CAAA;iBAAE,CAAC;gBAAC,OAAO,EAAE,UAAU,EAAE,CAAA;aAAE,CAAC,CAAC;SACrF;KACF,CAAC;CACH;AAED,2DAA2D;AAC3D,MAAM,WAAW,SAAS;IACxB,gFAAgF;IAChF,OAAO,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;IAC7F,8DAA8D;IAC9D,QAAQ,EAAE,SAAS;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,EAAE,CAAC;IAChF,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;CACvB;AASD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CAqG7E"}
package/dist/otlp.js ADDED
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Prompt cost as OpenTelemetry metrics, in the OTLP/HTTP JSON encoding.
3
+ *
4
+ * **Trazum writes the payload; it does not send it.** That is a decision, not a
5
+ * missing feature. Pushing to a collector would mean holding an endpoint and a
6
+ * credential, and this repository has twice shipped an SSRF where a URL reached
7
+ * `fetch` without being the URL that was checked. A command that writes a file
8
+ * has no such failure mode, and `curl --data-binary @metrics.json $OTLP_ENDPOINT`
9
+ * is one line in the pipeline that already has the credential.
10
+ *
11
+ * No dependency on `@opentelemetry/*` either — the JSON encoding is a stable,
12
+ * documented wire format, and this package has no runtime dependencies by design.
13
+ *
14
+ * The two encoding rules that are easy to get wrong and silently produce a
15
+ * payload a collector drops:
16
+ *
17
+ * - **64-bit integers are strings.** `timeUnixNano` and `asInt` are `int64` in
18
+ * the protobuf, and JSON numbers cannot hold one exactly, so the JSON mapping
19
+ * specifies strings. A collector reading `1786000000000000000` as a double
20
+ * loses the last digits of every timestamp.
21
+ * - **A gauge is `asDouble` or `asInt`, never both**, and a money figure is a
22
+ * double. Sending `asInt` for `$41.20` silently reports `41`.
23
+ */
24
+ const str = (key, value) => ({ key, value: { stringValue: value } });
25
+ /** Rounded to the nearest nanosecond as an integer string, never a float. */
26
+ function nanos(atMillis) {
27
+ return `${Math.round(atMillis)}000000`;
28
+ }
29
+ /**
30
+ * `atMillis` is a parameter rather than a call to `Date.now()`.
31
+ *
32
+ * The same reason `computeSavings` takes a `Date`: a function that reads the
33
+ * clock cannot be asserted against a fixed expectation, and every test of it
34
+ * ends up asserting only its shape. Here the timestamp *is* half the payload.
35
+ */
36
+ export function toOtlpMetrics(input, atMillis) {
37
+ const { prompts, findings, model, callsPerMonth } = input;
38
+ const time = nanos(atMillis);
39
+ const metrics = [];
40
+ const gauge = (name, description, unit, dataPoints) => {
41
+ // An empty metric is not the same as a zero, and a collector charting an
42
+ // empty series draws nothing rather than a flat line at zero. Omitted.
43
+ if (dataPoints.length > 0)
44
+ metrics.push({ name, description, unit, gauge: { dataPoints } });
45
+ };
46
+ gauge('trazum.prompt.tokens', 'Input tokens in a prompt as written, the figure a budget is compared against.', '{token}', prompts.map((p) => ({
47
+ attributes: [str('prompt.path', p.path)],
48
+ timeUnixNano: time,
49
+ asInt: `${Math.round(p.tokens)}`,
50
+ })));
51
+ // Per prompt rather than as one count, so a dashboard can name the offender
52
+ // instead of showing a number that went from 2 to 3.
53
+ gauge('trazum.prompt.over_budget', 'Whether this prompt exceeds the budget that covers it. 1 when it does.', '1', prompts
54
+ .filter((p) => p.budgeted)
55
+ .map((p) => ({
56
+ attributes: [str('prompt.path', p.path)],
57
+ timeUnixNano: time,
58
+ asInt: p.overBudget ? '1' : '0',
59
+ })));
60
+ gauge('trazum.prompts.unbudgeted', 'Prompts no budget pattern matches, so nothing is watching them.', '{prompt}', [
61
+ {
62
+ attributes: [],
63
+ timeUnixNano: time,
64
+ asInt: `${prompts.filter((p) => !p.budgeted).length}`,
65
+ },
66
+ ]);
67
+ // Money is a double. Sending this as asInt would report $41.20 as 41 and the
68
+ // chart would look plausible.
69
+ gauge('trazum.advisory.monthly_usd', 'Estimated monthly USD an advisory would recover, summed across the prompts raising it.', 'USD', findings
70
+ .filter((f) => f.monthlyUsd !== null)
71
+ .map((f) => ({
72
+ attributes: [str('advisory.id', f.id)],
73
+ timeUnixNano: time,
74
+ asDouble: f.monthlyUsd,
75
+ })));
76
+ gauge('trazum.advisory.prompts', 'How many prompts raise this advisory.', '{prompt}', findings.map((f) => ({
77
+ attributes: [str('advisory.id', f.id)],
78
+ timeUnixNano: time,
79
+ asInt: `${f.prompts}`,
80
+ })));
81
+ return {
82
+ resourceMetrics: [
83
+ {
84
+ // The model and call volume are resource attributes, not metric labels:
85
+ // every figure above was priced through them, and a dollar amount whose
86
+ // scenario is not recorded beside it is a number nobody can check later.
87
+ resource: {
88
+ attributes: [
89
+ str('service.name', 'trazum'),
90
+ str('trazum.model', model),
91
+ {
92
+ key: 'trazum.calls_per_month',
93
+ value: { intValue: `${Math.round(callsPerMonth)}` },
94
+ },
95
+ ],
96
+ },
97
+ scopeMetrics: [{ scope: { name: 'trazum', version: '1.0.0' }, metrics }],
98
+ },
99
+ ],
100
+ };
101
+ }
102
+ //# sourceMappingURL=otlp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otlp.js","sourceRoot":"","sources":["../src/otlp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Phrase dictionaries.
3
+ *
4
+ * These lists are DATA, not user interface: they are the vocabulary Trazum
5
+ * looks for inside the prompts it optimises. Real prompts mix languages, so
6
+ * the dictionaries deliberately cover English and Spanish at the same time and
7
+ * a single prompt can trigger entries from both.
8
+ *
9
+ * Adding a language here means adding entries to these lists — it is unrelated
10
+ * to the report language, which lives in `src/i18n/`.
11
+ *
12
+ * Every entry earns its place by preserving meaning. Anything whose removal
13
+ * could change what the prompt asks for belongs in the `aggressive` level, or
14
+ * does not belong here at all.
15
+ */
16
+ /**
17
+ * Languages the trimming dictionaries below actually cover.
18
+ *
19
+ * Exported because the report has to be able to say so. Until this existed,
20
+ * a French or German prompt came back with `No rule found anything to trim` —
21
+ * which reads as "your prompt is already efficient" and meant "I do not speak
22
+ * your language". Same class of mistake as the one `--reorder` had: the tool
23
+ * knew something it was not telling the reader.
24
+ *
25
+ * `phrases.test.js` asserts this list matches the languages the dictionaries are
26
+ * actually grouped under, so adding a dictionary without listing it — or listing
27
+ * one without the entries — fails rather than misleads.
28
+ */
29
+ export declare const PHRASE_LANGUAGES: readonly string[];
30
+ /** Long phrases and their shorter equivalent. */
31
+ export declare const VERBOSE_PHRASES: ReadonlyArray<readonly [string, string]>;
32
+ /** Courtesy: adds nothing for the model and costs tokens on every call. */
33
+ export declare const POLITENESS: readonly string[];
34
+ /** Filler and throat-clearing with no content. */
35
+ export declare const FILLER: readonly string[];
36
+ /** Intensifiers: they rarely change the task and almost always add tokens. */
37
+ /**
38
+ * Words that add emphasis and nothing else, so dropping one cannot change what
39
+ * the prompt asks for.
40
+ *
41
+ * **A quantifier is not an intensifier**, and the distinction is not visible in
42
+ * a dictionary translated word by word. Spanish gets `muy` and deliberately not
43
+ * `mucho`; the first draft of the other five languages did not keep that line
44
+ * and shipped `muito`, `molto` and `heel`, each of which does both jobs:
45
+ *
46
+ * Hai molto tempo per rispondere. → Hai tempo per rispondere.
47
+ *
48
+ * "You have much time" became "you have time". Caught by running the five
49
+ * languages through the rules rather than by reading the list, and pinned by a
50
+ * test that keeps those three words out.
51
+ */
52
+ export declare const INTENSIFIERS: readonly string[];
53
+ /** Hedges that weaken the instruction without adding information. */
54
+ export declare const HEDGES: readonly string[];
55
+ /**
56
+ * Self-verification instructions.
57
+ *
58
+ * On current models these trigger over-verification: the model already checks
59
+ * its own work, and asking explicitly fires extra steps paid for in output
60
+ * tokens. Aggressive level because some workflows do want explicit
61
+ * verification.
62
+ *
63
+ * The full forms come first on purpose: removing only the core phrase would
64
+ * leave a dangling fragment such as "Before answering." — worse than leaving
65
+ * the sentence alone.
66
+ *
67
+ * That includes whatever introduces the instruction. "You MUST double-check
68
+ * your answer before responding" reduced to "You must." for exactly this
69
+ * reason: the phrase matched, the subject and modal in front of it did not,
70
+ * and what survived was a sentence that says nothing. Anything that can open
71
+ * one of these instructions belongs in the list, ahead of the bare form.
72
+ */
73
+ export declare const SELF_CHECK: readonly string[];
74
+ /** Words shouted in capitals that cost fewer tokens in lowercase. */
75
+ export declare const SHOUTED_WORDS: readonly string[];
76
+ /** Emphasis prefixes at the start of a line. */
77
+ export declare const EMPHASIS_PREFIXES: readonly string[];
78
+ /**
79
+ * Vocabulary suggesting the task needs a more capable model.
80
+ * Multilingual for the same reason as the dictionaries above.
81
+ */
82
+ export declare const COMPLEX_SIGNALS: readonly string[];
83
+ /** Vocabulary suggesting a cheaper model would do. */
84
+ export declare const SIMPLE_SIGNALS: readonly string[];
85
+ /**
86
+ * Phrases that refer *backwards* to something earlier in the prompt, by language.
87
+ *
88
+ * These are what makes reordering unsafe. "Summarise the text above" is correct
89
+ * where it sits and nonsense if moved in front of the text it points at, so a
90
+ * block containing one of these must stay where it is — and so must everything
91
+ * after it, because moving a later block past a pinned one changes their order
92
+ * relative to each other.
93
+ *
94
+ * Deliberately generous. A false positive costs a saving that was available; a
95
+ * false negative silently changes what the prompt asks for, which is the one
96
+ * thing this project will not trade for tokens.
97
+ *
98
+ * **Grouped by language rather than kept as one flat list, because the flat list
99
+ * hid a hole.** It held English and Spanish and was applied to every prompt, so
100
+ * a French, German or Japanese author ran `--reorder` with no protection at all:
101
+ * every refusal this module is built on silently did not apply to them, and
102
+ * "Résumez le texte ci-dessus" was hoisted above the text and reported as a
103
+ * saving. A list per language makes the coverage a thing you can look at, and
104
+ * lets `UNCOVERED_SCRIPTS` below refuse what is still missing rather than
105
+ * pretending.
106
+ *
107
+ * Every language is matched against every prompt. Language detection would be
108
+ * one more thing to get wrong, and the cost of checking a French prompt for
109
+ * German phrases is a missed saving, which is the direction this module errs in
110
+ * anyway.
111
+ */
112
+ export interface BackwardReferenceSet {
113
+ /**
114
+ * Whether a match must sit on a word boundary.
115
+ *
116
+ * True for anything written with spaces, so "aboveboard" does not pin a block.
117
+ * False for Japanese and Chinese, which have no word boundaries at all — the
118
+ * boundary test asks whether the neighbouring character is a letter, and in
119
+ * 上記のテキスト it always is, so a boundary-matched CJK phrase would never
120
+ * fire. A list that cannot match is worse than no list: it reads like cover.
121
+ */
122
+ wordBoundaries: boolean;
123
+ phrases: readonly string[];
124
+ }
125
+ export declare const BACKWARD_REFERENCES_BY_LANGUAGE: Readonly<Record<string, BackwardReferenceSet>>;
126
+ /**
127
+ * Every phrase, flattened. Kept for callers that only need the words.
128
+ */
129
+ export declare const BACKWARD_REFERENCES: readonly string[];
130
+ /**
131
+ * Scripts this module has no backward-reference phrases for.
132
+ *
133
+ * The point of naming them is that the alternative is what used to happen:
134
+ * rearranging a Russian or Arabic prompt with nothing to stop it, and calling
135
+ * the result a saving. `reorderForCache` refuses when it sees one of these,
136
+ * which turns a silent hazard into a message somebody can act on — and into a
137
+ * short list of pull requests, since adding a language is adding an array.
138
+ *
139
+ * Matched on the script, not the language: the question is not "which language
140
+ * is this" but "is there any chance my phrase lists apply to it".
141
+ */
142
+ export declare const UNCOVERED_SCRIPTS: ReadonlyArray<{
143
+ name: string;
144
+ pattern: RegExp;
145
+ }>;
146
+ /**
147
+ * Phrases that mark a block as the *output contract* rather than as data.
148
+ *
149
+ * Needed because a fenced JSON block in a prompt is one of two completely
150
+ * different things, and confusing them is the one way this analysis could do
151
+ * harm. `Output format: {...}` describes what the model must return, and a
152
+ * provider that accepts a response schema takes it as a request parameter
153
+ * instead — those tokens leave the prompt on every call. `Input: {...}` inside a
154
+ * few-shot example is *data the prompt needs*, and advising anyone to move it
155
+ * would break their prompt.
156
+ *
157
+ * Nothing here guesses. A schema block with no such phrase before it is left
158
+ * alone, because "probably an output format" is not good enough when being wrong
159
+ * costs somebody a working prompt.
160
+ *
161
+ * Seven languages, the same set the rules cover. A prompt in a language absent
162
+ * here raises nothing, which is a false negative and states itself as one — the
163
+ * alternative is matching an English cue inside Japanese prose and calling the
164
+ * result a saving.
165
+ */
166
+ export declare const OUTPUT_CUES_BY_LANGUAGE: Readonly<Record<string, readonly string[]>>;
167
+ /** Every output cue, flattened. Order is not significant. */
168
+ export declare const OUTPUT_CUES: readonly string[];
169
+ //# sourceMappingURL=phrases.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phrases.d.ts","sourceRoot":"","sources":["../src/phrases.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAA+C,CAAC;AAE9F,iDAAiD;AACjD,eAAO,MAAM,eAAe,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CA4IpE,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,UAAU,EAAE,SAAS,MAAM,EA8DvC,CAAC;AAEF,kDAAkD;AAClD,eAAO,MAAM,MAAM,EAAE,SAAS,MAAM,EAiEnC,CAAC;AAEF,8EAA8E;AAC9E;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAkDzC,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,MAAM,EAAE,SAAS,MAAM,EA0CnC,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,UAAU,EAAE,SAAS,MAAM,EAqFvC,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAoB1C,CAAC;AAEF,gDAAgD;AAChD,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAe9C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,MAAM,EAkC5C,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAsB3C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;;;;OAQG;IACH,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B;AAED,eAAO,MAAM,+BAA+B,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAiN1F,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAElB,CAAC;AAEhC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAQ9E,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CA+G/E,CAAC;AAEF,6DAA6D;AAC7D,eAAO,MAAM,WAAW,EAAE,SAAS,MAAM,EAAkD,CAAC"}