@trazum/core 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +163 -0
  3. package/dist/advisories.d.ts +23 -0
  4. package/dist/advisories.d.ts.map +1 -0
  5. package/dist/advisories.js +376 -0
  6. package/dist/advisories.js.map +1 -0
  7. package/dist/aws-sigv4.d.ts +88 -0
  8. package/dist/aws-sigv4.d.ts.map +1 -0
  9. package/dist/aws-sigv4.js +117 -0
  10. package/dist/aws-sigv4.js.map +1 -0
  11. package/dist/baseline.d.ts +171 -0
  12. package/dist/baseline.d.ts.map +1 -0
  13. package/dist/baseline.js +273 -0
  14. package/dist/baseline.js.map +1 -0
  15. package/dist/cache.d.ts +26 -0
  16. package/dist/cache.d.ts.map +1 -0
  17. package/dist/cache.js +28 -0
  18. package/dist/cache.js.map +1 -0
  19. package/dist/changes.d.ts +29 -0
  20. package/dist/changes.d.ts.map +1 -0
  21. package/dist/changes.js +142 -0
  22. package/dist/changes.js.map +1 -0
  23. package/dist/compare.d.ts +65 -0
  24. package/dist/compare.d.ts.map +1 -0
  25. package/dist/compare.js +58 -0
  26. package/dist/compare.js.map +1 -0
  27. package/dist/config-schema.d.ts +118 -0
  28. package/dist/config-schema.d.ts.map +1 -0
  29. package/dist/config-schema.js +315 -0
  30. package/dist/config-schema.js.map +1 -0
  31. package/dist/config.d.ts +47 -0
  32. package/dist/config.d.ts.map +1 -0
  33. package/dist/config.js +112 -0
  34. package/dist/config.js.map +1 -0
  35. package/dist/detect.d.ts +70 -0
  36. package/dist/detect.d.ts.map +1 -0
  37. package/dist/detect.js +228 -0
  38. package/dist/detect.js.map +1 -0
  39. package/dist/evaluate.d.ts +98 -0
  40. package/dist/evaluate.d.ts.map +1 -0
  41. package/dist/evaluate.js +110 -0
  42. package/dist/evaluate.js.map +1 -0
  43. package/dist/extract.d.ts +81 -0
  44. package/dist/extract.d.ts.map +1 -0
  45. package/dist/extract.js +280 -0
  46. package/dist/extract.js.map +1 -0
  47. package/dist/gcp-auth.d.ts +58 -0
  48. package/dist/gcp-auth.d.ts.map +1 -0
  49. package/dist/gcp-auth.js +113 -0
  50. package/dist/gcp-auth.js.map +1 -0
  51. package/dist/glob.d.ts +49 -0
  52. package/dist/glob.d.ts.map +1 -0
  53. package/dist/glob.js +154 -0
  54. package/dist/glob.js.map +1 -0
  55. package/dist/host.d.ts +30 -0
  56. package/dist/host.d.ts.map +1 -0
  57. package/dist/host.js +69 -0
  58. package/dist/host.js.map +1 -0
  59. package/dist/i18n/en.d.ts +4 -0
  60. package/dist/i18n/en.d.ts.map +1 -0
  61. package/dist/i18n/en.js +168 -0
  62. package/dist/i18n/en.js.map +1 -0
  63. package/dist/i18n/es.d.ts +4 -0
  64. package/dist/i18n/es.d.ts.map +1 -0
  65. package/dist/i18n/es.js +168 -0
  66. package/dist/i18n/es.js.map +1 -0
  67. package/dist/i18n/index.d.ts +36 -0
  68. package/dist/i18n/index.d.ts.map +1 -0
  69. package/dist/i18n/index.js +50 -0
  70. package/dist/i18n/index.js.map +1 -0
  71. package/dist/i18n/types.d.ts +180 -0
  72. package/dist/i18n/types.d.ts.map +1 -0
  73. package/dist/i18n/types.js +11 -0
  74. package/dist/i18n/types.js.map +1 -0
  75. package/dist/index.d.ts +66 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +69 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/llm.d.ts +226 -0
  80. package/dist/llm.d.ts.map +1 -0
  81. package/dist/llm.js +485 -0
  82. package/dist/llm.js.map +1 -0
  83. package/dist/nearest.d.ts +20 -0
  84. package/dist/nearest.d.ts.map +1 -0
  85. package/dist/nearest.js +54 -0
  86. package/dist/nearest.js.map +1 -0
  87. package/dist/net.d.ts +90 -0
  88. package/dist/net.d.ts.map +1 -0
  89. package/dist/net.js +203 -0
  90. package/dist/net.js.map +1 -0
  91. package/dist/node.d.ts +32 -0
  92. package/dist/node.d.ts.map +1 -0
  93. package/dist/node.js +33 -0
  94. package/dist/node.js.map +1 -0
  95. package/dist/openrouter.d.ts +25 -0
  96. package/dist/openrouter.d.ts.map +1 -0
  97. package/dist/openrouter.js +72 -0
  98. package/dist/openrouter.js.map +1 -0
  99. package/dist/optimize.d.ts +38 -0
  100. package/dist/optimize.d.ts.map +1 -0
  101. package/dist/optimize.js +183 -0
  102. package/dist/optimize.js.map +1 -0
  103. package/dist/otlp.d.ts +91 -0
  104. package/dist/otlp.d.ts.map +1 -0
  105. package/dist/otlp.js +102 -0
  106. package/dist/otlp.js.map +1 -0
  107. package/dist/phrases.d.ts +169 -0
  108. package/dist/phrases.d.ts.map +1 -0
  109. package/dist/phrases.js +939 -0
  110. package/dist/phrases.js.map +1 -0
  111. package/dist/pricing-overlay.d.ts +55 -0
  112. package/dist/pricing-overlay.d.ts.map +1 -0
  113. package/dist/pricing-overlay.js +241 -0
  114. package/dist/pricing-overlay.js.map +1 -0
  115. package/dist/pricing.d.ts +115 -0
  116. package/dist/pricing.d.ts.map +1 -0
  117. package/dist/pricing.js +400 -0
  118. package/dist/pricing.js.map +1 -0
  119. package/dist/profile.d.ts +71 -0
  120. package/dist/profile.d.ts.map +1 -0
  121. package/dist/profile.js +55 -0
  122. package/dist/profile.js.map +1 -0
  123. package/dist/promptfoo.d.ts +58 -0
  124. package/dist/promptfoo.d.ts.map +1 -0
  125. package/dist/promptfoo.js +149 -0
  126. package/dist/promptfoo.js.map +1 -0
  127. package/dist/prune.d.ts +91 -0
  128. package/dist/prune.d.ts.map +1 -0
  129. package/dist/prune.js +110 -0
  130. package/dist/prune.js.map +1 -0
  131. package/dist/reorder.d.ts +82 -0
  132. package/dist/reorder.d.ts.map +1 -0
  133. package/dist/reorder.js +215 -0
  134. package/dist/reorder.js.map +1 -0
  135. package/dist/review.d.ts +54 -0
  136. package/dist/review.d.ts.map +1 -0
  137. package/dist/review.js +131 -0
  138. package/dist/review.js.map +1 -0
  139. package/dist/rules.d.ts +5 -0
  140. package/dist/rules.d.ts.map +1 -0
  141. package/dist/rules.js +279 -0
  142. package/dist/rules.js.map +1 -0
  143. package/dist/savings.d.ts +36 -0
  144. package/dist/savings.d.ts.map +1 -0
  145. package/dist/savings.js +83 -0
  146. package/dist/savings.js.map +1 -0
  147. package/dist/segment.d.ts +8 -0
  148. package/dist/segment.d.ts.map +1 -0
  149. package/dist/segment.js +74 -0
  150. package/dist/segment.js.map +1 -0
  151. package/dist/shared-prefix.d.ts +63 -0
  152. package/dist/shared-prefix.d.ts.map +1 -0
  153. package/dist/shared-prefix.js +151 -0
  154. package/dist/shared-prefix.js.map +1 -0
  155. package/dist/similarity.d.ts +13 -0
  156. package/dist/similarity.d.ts.map +1 -0
  157. package/dist/similarity.js +30 -0
  158. package/dist/similarity.js.map +1 -0
  159. package/dist/structure.d.ts +144 -0
  160. package/dist/structure.d.ts.map +1 -0
  161. package/dist/structure.js +455 -0
  162. package/dist/structure.js.map +1 -0
  163. package/dist/suggest.d.ts +100 -0
  164. package/dist/suggest.d.ts.map +1 -0
  165. package/dist/suggest.js +151 -0
  166. package/dist/suggest.js.map +1 -0
  167. package/dist/tokenizer.d.ts +57 -0
  168. package/dist/tokenizer.d.ts.map +1 -0
  169. package/dist/tokenizer.js +157 -0
  170. package/dist/tokenizer.js.map +1 -0
  171. package/dist/types.d.ts +296 -0
  172. package/dist/types.d.ts.map +1 -0
  173. package/dist/types.js +3 -0
  174. package/dist/types.js.map +1 -0
  175. package/dist/walk.d.ts +40 -0
  176. package/dist/walk.d.ts.map +1 -0
  177. package/dist/walk.js +95 -0
  178. package/dist/walk.js.map +1 -0
  179. package/package.json +56 -0
  180. package/src/advisories.ts +431 -0
  181. package/src/aws-sigv4.ts +174 -0
  182. package/src/baseline.ts +390 -0
  183. package/src/cache.ts +54 -0
  184. package/src/changes.ts +158 -0
  185. package/src/compare.ts +131 -0
  186. package/src/config-schema.ts +451 -0
  187. package/src/config.ts +161 -0
  188. package/src/detect.ts +312 -0
  189. package/src/evaluate.ts +188 -0
  190. package/src/extract.ts +336 -0
  191. package/src/gcp-auth.ts +166 -0
  192. package/src/glob.ts +160 -0
  193. package/src/host.ts +90 -0
  194. package/src/i18n/en.ts +236 -0
  195. package/src/i18n/es.ts +236 -0
  196. package/src/i18n/index.ts +68 -0
  197. package/src/i18n/types.ts +230 -0
  198. package/src/index.ts +228 -0
  199. package/src/llm.ts +708 -0
  200. package/src/nearest.ts +61 -0
  201. package/src/net.ts +233 -0
  202. package/src/node.ts +63 -0
  203. package/src/openrouter.ts +125 -0
  204. package/src/optimize.ts +228 -0
  205. package/src/otlp.ts +179 -0
  206. package/src/phrases.ts +1047 -0
  207. package/src/pricing-overlay.ts +319 -0
  208. package/src/pricing.ts +468 -0
  209. package/src/profile.ts +124 -0
  210. package/src/promptfoo.ts +213 -0
  211. package/src/prune.ts +211 -0
  212. package/src/reorder.ts +307 -0
  213. package/src/review.ts +180 -0
  214. package/src/rules.ts +324 -0
  215. package/src/savings.ts +121 -0
  216. package/src/segment.ts +106 -0
  217. package/src/shared-prefix.ts +198 -0
  218. package/src/similarity.ts +28 -0
  219. package/src/structure.ts +652 -0
  220. package/src/suggest.ts +254 -0
  221. package/src/tokenizer.ts +190 -0
  222. package/src/types.ts +323 -0
  223. package/src/walk.ts +117 -0
package/dist/net.d.ts ADDED
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Endpoint validation for the pluggable LLM layer.
3
+ *
4
+ * Trazum lets the caller choose the URL its optional LLM pass talks to. On a
5
+ * deployed server that is a server-side request forgery primitive: without
6
+ * this check, anyone who can reach the web app can make it fetch the cloud
7
+ * metadata service, an internal admin panel, or any host behind the firewall,
8
+ * and read the response through the error message.
9
+ *
10
+ * This lives in the core rather than in the web route on purpose. It is the
11
+ * most security-sensitive code in the project, so it belongs where it can be
12
+ * unit-tested and where every caller — the API route today, anything else
13
+ * later — gets the same answer.
14
+ */
15
+ /** Whether a hostname points somewhere a public service must not fetch. */
16
+ export declare function isPrivateHost(hostname: string): boolean;
17
+ export type EndpointRejection = 'invalid-url' | 'insecure-scheme' | 'private-host' | 'credentials-in-url';
18
+ export interface ValidateEndpointOptions {
19
+ /**
20
+ * Allow `http:` and private hosts. For local development only — it disables
21
+ * the entire protection, so it must never be derived from request input.
22
+ */
23
+ allowInsecure?: boolean;
24
+ }
25
+ /**
26
+ * Validates an LLM endpoint URL. Returns `null` when it is safe to fetch, or a
27
+ * machine-readable reason when it is not.
28
+ *
29
+ * A reason code rather than a message so the caller renders it in the reader's
30
+ * locale, and so a test asserts on the decision rather than on wording.
31
+ */
32
+ export declare function validateLlmEndpoint(raw: string, options?: ValidateEndpointOptions): EndpointRejection | null;
33
+ /**
34
+ * Validates an endpoint and returns the value to fetch, with no trailing slash.
35
+ *
36
+ * Returning it rather than approving it is the point. The first version of this
37
+ * validated `baseUrl` and then fetched
38
+ * `` `${baseUrl.replace(/\/$/, '')}/chat/completions` `` — two different
39
+ * expressions, so the thing checked was never the thing used, and a later edit
40
+ * could have moved the check without anything noticing.
41
+ *
42
+ * Re-parsing normalises it too: `https://host/v1/../../admin` passes validation
43
+ * as a string and resolves somewhere else on the wire.
44
+ *
45
+ * Every caller in this package that sends a key to a caller-named host goes
46
+ * through here — both providers and the exact token counter.
47
+ */
48
+ export declare function checkedEndpoint(baseUrl: string, { allowInsecure, name }: ValidateEndpointOptions & {
49
+ name: string;
50
+ }): string;
51
+ /**
52
+ * `fetch` options that every server-side call in this package must carry.
53
+ *
54
+ * `redirect: 'error'` is the one that matters, and it was missing. Everything
55
+ * above validates the URL the caller named — and then `fetch` followed
56
+ * redirects by default, so an endpoint that passed every check could answer
57
+ * `302 Location: http://169.254.169.254/latest/meta-data/` and the request went
58
+ * there anyway, carrying the `authorization` header. The entire host filter was
59
+ * one HTTP response away from being bypassed, for the CLI as much as for the
60
+ * deployed app.
61
+ *
62
+ * A refused redirect is a thrown `TypeError`, which is the right outcome: a
63
+ * legitimate LLM endpoint does not redirect its completions API, and one that
64
+ * suddenly does is exactly the case worth failing on.
65
+ */
66
+ export declare const SAFE_FETCH_INIT: {
67
+ readonly redirect: 'error';
68
+ readonly credentials: 'omit';
69
+ readonly referrerPolicy: 'no-referrer';
70
+ };
71
+ /**
72
+ * The endpoints this server will call, from `TRAZUM_ALLOWED_LLM_ENDPOINTS`
73
+ * (comma-separated).
74
+ *
75
+ * An entry that would fail `validateLlmEndpoint` is dropped rather than
76
+ * honoured. The operator is trusted to choose, not to be immune from pasting
77
+ * `http://169.254.169.254` into a list that then serves every anonymous caller.
78
+ * `allowInsecure` is deliberately not offered: an endpoint only reachable with
79
+ * the protection off has no business being selectable over HTTP.
80
+ */
81
+ export declare function allowedEndpoints(env: Record<string, string | undefined>): readonly string[];
82
+ /**
83
+ * Resolves what a caller asked for to an entry on the list.
84
+ *
85
+ * Returns the **listed** value, not the requested one. That distinction is the
86
+ * entire function: the string from the request is compared and then discarded,
87
+ * so nothing derived from it reaches `fetch`.
88
+ */
89
+ export declare function resolveEndpoint(requested: string, allowed: readonly string[]): string | null;
90
+ //# sourceMappingURL=net.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"net.d.ts","sourceRoot":"","sources":["../src/net.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAiCH,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAIvD;AAED,MAAM,MAAM,iBAAiB,GACzB,aAAa,GACb,iBAAiB,GACjB,cAAc,GACd,oBAAoB,CAAC;AAEzB,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,uBAA4B,GACpC,iBAAiB,GAAG,IAAI,CAyB1B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,EAAE,aAAqB,EAAE,IAAI,EAAE,EAAE,uBAAuB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAC1E,MAAM,CASR;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,eAAe;uBAChB,OAAO;0BAEJ,MAAM;6BACH,aAAa;CACC,CAAC;AA4CjC;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,SAAS,MAAM,EAAE,CAY3F;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAI5F"}
package/dist/net.js ADDED
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Endpoint validation for the pluggable LLM layer.
3
+ *
4
+ * Trazum lets the caller choose the URL its optional LLM pass talks to. On a
5
+ * deployed server that is a server-side request forgery primitive: without
6
+ * this check, anyone who can reach the web app can make it fetch the cloud
7
+ * metadata service, an internal admin panel, or any host behind the firewall,
8
+ * and read the response through the error message.
9
+ *
10
+ * This lives in the core rather than in the web route on purpose. It is the
11
+ * most security-sensitive code in the project, so it belongs where it can be
12
+ * unit-tested and where every caller — the API route today, anything else
13
+ * later — gets the same answer.
14
+ */
15
+ /**
16
+ * Hostnames that must never be reachable from a server-side fetch.
17
+ *
18
+ * Written against the *hostname string* rather than a resolved address: DNS
19
+ * resolution would be more thorough but introduces a TOCTOU window (the name
20
+ * can resolve differently between the check and the request) and a network
21
+ * call in a validation path. This blocks the literal forms an attacker
22
+ * actually types; defence in depth against DNS rebinding belongs at the egress
23
+ * layer, and is called out in SECURITY.md rather than pretended to here.
24
+ */
25
+ const PRIVATE_HOST_PATTERNS = [
26
+ /^localhost$/i,
27
+ /^127\./,
28
+ /^0\./,
29
+ /^10\./,
30
+ /^172\.(1[6-9]|2\d|3[01])\./,
31
+ /^192\.168\./,
32
+ /^169\.254\./, // cloud metadata (AWS/GCP/Azure) and link-local
33
+ /^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./, // carrier-grade NAT
34
+ /^\[?::1\]?$/,
35
+ /^\[?::\]?$/,
36
+ /^\[?f[cd][0-9a-f]{2}:/i, // IPv6 unique local
37
+ /^\[?fe80:/i, // IPv6 link-local
38
+ /^\[?::ffff:/i, // IPv4-mapped IPv6, e.g. ::ffff:169.254.169.254
39
+ /\.internal$/i,
40
+ /\.local$/i,
41
+ /\.localhost$/i,
42
+ /^metadata\./i,
43
+ /^metadata$/i,
44
+ ];
45
+ /** Whether a hostname points somewhere a public service must not fetch. */
46
+ export function isPrivateHost(hostname) {
47
+ const host = hostname.trim().toLowerCase().replace(/\.$/, '');
48
+ if (!host)
49
+ return true;
50
+ return PRIVATE_HOST_PATTERNS.some((pattern) => pattern.test(host));
51
+ }
52
+ /**
53
+ * Validates an LLM endpoint URL. Returns `null` when it is safe to fetch, or a
54
+ * machine-readable reason when it is not.
55
+ *
56
+ * A reason code rather than a message so the caller renders it in the reader's
57
+ * locale, and so a test asserts on the decision rather than on wording.
58
+ */
59
+ export function validateLlmEndpoint(raw, options = {}) {
60
+ let url;
61
+ try {
62
+ url = new URL(raw);
63
+ }
64
+ catch {
65
+ return 'invalid-url';
66
+ }
67
+ const { allowInsecure = false } = options;
68
+ if (url.protocol !== 'https:' && !(allowInsecure && url.protocol === 'http:')) {
69
+ return 'insecure-scheme';
70
+ }
71
+ // Credentials in the URL would be forwarded to whatever the host turns out
72
+ // to be, and would land in any log line that records the endpoint.
73
+ if (url.username || url.password) {
74
+ return 'credentials-in-url';
75
+ }
76
+ if (!allowInsecure && isPrivateHost(url.hostname)) {
77
+ return 'private-host';
78
+ }
79
+ return null;
80
+ }
81
+ /**
82
+ * Validates an endpoint and returns the value to fetch, with no trailing slash.
83
+ *
84
+ * Returning it rather than approving it is the point. The first version of this
85
+ * validated `baseUrl` and then fetched
86
+ * `` `${baseUrl.replace(/\/$/, '')}/chat/completions` `` — two different
87
+ * expressions, so the thing checked was never the thing used, and a later edit
88
+ * could have moved the check without anything noticing.
89
+ *
90
+ * Re-parsing normalises it too: `https://host/v1/../../admin` passes validation
91
+ * as a string and resolves somewhere else on the wire.
92
+ *
93
+ * Every caller in this package that sends a key to a caller-named host goes
94
+ * through here — both providers and the exact token counter.
95
+ */
96
+ export function checkedEndpoint(baseUrl, { allowInsecure = false, name }) {
97
+ const rejection = validateLlmEndpoint(baseUrl, { allowInsecure });
98
+ if (rejection !== null) {
99
+ throw new Error(`Provider "${name}" cannot use ${baseUrl}: ${rejection}. ` +
100
+ 'Pass allowInsecure only for an endpoint you configured yourself.');
101
+ }
102
+ return new URL(baseUrl).toString().replace(/\/$/, '');
103
+ }
104
+ /**
105
+ * `fetch` options that every server-side call in this package must carry.
106
+ *
107
+ * `redirect: 'error'` is the one that matters, and it was missing. Everything
108
+ * above validates the URL the caller named — and then `fetch` followed
109
+ * redirects by default, so an endpoint that passed every check could answer
110
+ * `302 Location: http://169.254.169.254/latest/meta-data/` and the request went
111
+ * there anyway, carrying the `authorization` header. The entire host filter was
112
+ * one HTTP response away from being bypassed, for the CLI as much as for the
113
+ * deployed app.
114
+ *
115
+ * A refused redirect is a thrown `TypeError`, which is the right outcome: a
116
+ * legitimate LLM endpoint does not redirect its completions API, and one that
117
+ * suddenly does is exactly the case worth failing on.
118
+ */
119
+ export const SAFE_FETCH_INIT = {
120
+ redirect: 'error',
121
+ // No cookies or TLS client certs on a cross-origin call the caller named.
122
+ credentials: 'omit',
123
+ referrerPolicy: 'no-referrer',
124
+ };
125
+ // --------------------------------------------------------------------------
126
+ // What a deployment is willing to be pointed at
127
+ // --------------------------------------------------------------------------
128
+ /**
129
+ * Which LLM endpoints a server-side caller is allowed to reach.
130
+ *
131
+ * The web route used to take `baseUrl` out of the request body, validate the
132
+ * string and hand it to a provider. That is server-side request forgery by
133
+ * construction, and validating harder does not fix it:
134
+ *
135
+ * - Every check above reads the *name*. `https://totally-fine.example.com` that
136
+ * resolves to `169.254.169.254` passes all of them, and the fix for that is
137
+ * pinning the resolved address at the socket, which `fetch` does not offer.
138
+ * - Even with the redirect hop closed, an anonymous caller could still aim the
139
+ * server at any public host and read the reply through the error body.
140
+ * "Public" is not the same as "fine to fetch on somebody else's behalf".
141
+ *
142
+ * So the request body no longer *names* an endpoint. It **selects** one the
143
+ * operator listed, and the value that reaches the provider is the entry from
144
+ * the list — never the string that arrived over HTTP. The default is an empty
145
+ * list, so a deployment that has not thought about this cannot be pointed
146
+ * anywhere at all.
147
+ *
148
+ * Nobody loses the capability: an operator running Trazum for themselves puts
149
+ * the endpoint in `TRAZUM_LLM_BASE_URL` and it is used directly, exactly as the
150
+ * CLI has always worked. This is only about who is allowed to choose.
151
+ *
152
+ * `env` is a parameter rather than a read of `process.env` because this module
153
+ * is on the browser-safe path.
154
+ */
155
+ const ALLOWLIST_VAR = 'TRAZUM_ALLOWED_LLM_ENDPOINTS';
156
+ /** Normalised, so a trailing slash or a capitalised host is not a mismatch. */
157
+ function normaliseEndpoint(raw) {
158
+ try {
159
+ return new URL(raw.trim()).toString().replace(/\/$/, '');
160
+ }
161
+ catch {
162
+ return null;
163
+ }
164
+ }
165
+ /**
166
+ * The endpoints this server will call, from `TRAZUM_ALLOWED_LLM_ENDPOINTS`
167
+ * (comma-separated).
168
+ *
169
+ * An entry that would fail `validateLlmEndpoint` is dropped rather than
170
+ * honoured. The operator is trusted to choose, not to be immune from pasting
171
+ * `http://169.254.169.254` into a list that then serves every anonymous caller.
172
+ * `allowInsecure` is deliberately not offered: an endpoint only reachable with
173
+ * the protection off has no business being selectable over HTTP.
174
+ */
175
+ export function allowedEndpoints(env) {
176
+ const raw = env[ALLOWLIST_VAR];
177
+ if (!raw)
178
+ return [];
179
+ const listed = new Set();
180
+ for (const part of raw.split(',')) {
181
+ const url = normaliseEndpoint(part);
182
+ if (url === null)
183
+ continue;
184
+ if (validateLlmEndpoint(url) !== null)
185
+ continue;
186
+ listed.add(url);
187
+ }
188
+ return [...listed];
189
+ }
190
+ /**
191
+ * Resolves what a caller asked for to an entry on the list.
192
+ *
193
+ * Returns the **listed** value, not the requested one. That distinction is the
194
+ * entire function: the string from the request is compared and then discarded,
195
+ * so nothing derived from it reaches `fetch`.
196
+ */
197
+ export function resolveEndpoint(requested, allowed) {
198
+ const wanted = normaliseEndpoint(requested);
199
+ if (wanted === null)
200
+ return null;
201
+ return allowed.find((entry) => entry.toLowerCase() === wanted.toLowerCase()) ?? null;
202
+ }
203
+ //# sourceMappingURL=net.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"net.js","sourceRoot":"","sources":["../src/net.ts"],"names":[],"mappings":"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"}
package/dist/node.d.ts ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The parts of the core that read the filesystem.
3
+ *
4
+ * Kept off the main entry point on purpose, and the reason is a build failure
5
+ * rather than a preference: `apps/web` bundles `@trazum/core` for the browser,
6
+ * and a single `node:fs/promises` import anywhere in that graph fails the build
7
+ * outright — "the chunking context does not support external modules".
8
+ *
9
+ * That failure is the friendly version of the real risk. The web app hands
10
+ * `optimize()` a prompt straight from a request body; a file read reachable
11
+ * from that entry point is path traversal available to anyone who can reach the
12
+ * API. Two entry points make the split structural: the browser cannot import
13
+ * what is not in its graph, whatever a future refactor does.
14
+ *
15
+ * Only the CLI imports this.
16
+ */
17
+ export { loadConfig } from './config.js';
18
+ export type { LoadConfigOptions, LoadedConfig } from './config.js';
19
+ export { CONFIG_FILENAME, CONFIG_KEYS, CONFIG_USAGE_KEYS, ConfigError, DEFAULT_EXTENSIONS, MAX_CONFIG_BYTES, MAX_CONFIG_SEARCH_DEPTH, budgetFor, parseConfig, validateConfigModel, } from './config-schema.js';
20
+ export type { ResolvedBudget, TrazumConfig } from './config-schema.js';
21
+ export { MAX_PRICING_BYTES, PricingOverlayError, applyPricingOverlay, catalogueFromOverlay, parsePricingOverlay, } from './pricing-overlay.js';
22
+ export type { PricingOverlay } from './pricing-overlay.js';
23
+ export { openrouterOverlay } from './openrouter.js';
24
+ export type { OpenRouterResult } from './openrouter.js';
25
+ export { SAFE_FETCH_INIT, checkedEndpoint } from './net.js';
26
+ export { BUNDLED_CATALOGUE } from './pricing.js';
27
+ export type { PricingCatalogue } from './pricing.js';
28
+ export { detectHost } from './host.js';
29
+ export type { HostEnvironment } from './host.js';
30
+ export { MAX_WALK_DEPTH, MAX_WALK_FILES, walkPrompts } from './walk.js';
31
+ export type { WalkOptions, WalkResult } from './walk.js';
32
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAInE,OAAO,EACL,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,uBAAuB,EACvB,SAAS,EACT,WAAW,EACX,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvE,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGxD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC"}
package/dist/node.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The parts of the core that read the filesystem.
3
+ *
4
+ * Kept off the main entry point on purpose, and the reason is a build failure
5
+ * rather than a preference: `apps/web` bundles `@trazum/core` for the browser,
6
+ * and a single `node:fs/promises` import anywhere in that graph fails the build
7
+ * outright — "the chunking context does not support external modules".
8
+ *
9
+ * That failure is the friendly version of the real risk. The web app hands
10
+ * `optimize()` a prompt straight from a request body; a file read reachable
11
+ * from that entry point is path traversal available to anyone who can reach the
12
+ * API. Two entry points make the split structural: the browser cannot import
13
+ * what is not in its graph, whatever a future refactor does.
14
+ *
15
+ * Only the CLI imports this.
16
+ */
17
+ export { loadConfig } from './config.js';
18
+ // Re-exported for convenience, so the CLI has one import for everything
19
+ // config-shaped. These halves are pure and also live on the main entry point.
20
+ export { CONFIG_FILENAME, CONFIG_KEYS, CONFIG_USAGE_KEYS, ConfigError, DEFAULT_EXTENSIONS, MAX_CONFIG_BYTES, MAX_CONFIG_SEARCH_DEPTH, budgetFor, parseConfig, validateConfigModel, } from './config-schema.js';
21
+ // Local price corrections. Pure, so also on the main entry point; re-exported
22
+ // here so the CLI has one import for everything it needs to resolve a run.
23
+ export { MAX_PRICING_BYTES, PricingOverlayError, applyPricingOverlay, catalogueFromOverlay, parsePricingOverlay, } from './pricing-overlay.js';
24
+ // Turns a live price feed into an overlay. Pure — the fetch is the CLI's — and
25
+ // re-exported here for the same reason as the overlay itself: one import.
26
+ export { openrouterOverlay } from './openrouter.js';
27
+ // The endpoint gate every outbound call in this project goes through.
28
+ export { SAFE_FETCH_INIT, checkedEndpoint } from './net.js';
29
+ export { BUNDLED_CATALOGUE } from './pricing.js';
30
+ // Reads the process environment, so it cannot live on the browser-safe entry.
31
+ export { detectHost } from './host.js';
32
+ export { MAX_WALK_DEPTH, MAX_WALK_FILES, walkPrompts } from './walk.js';
33
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,wEAAwE;AACxE,8EAA8E;AAC9E,OAAO,EACL,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,uBAAuB,EACvB,SAAS,EACT,WAAW,EACX,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAG5B,8EAA8E;AAC9E,2EAA2E;AAC3E,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAG9B,+EAA+E;AAC/E,0EAA0E;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAGpD,sEAAsE;AACtE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGjD,8EAA8E;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAGvC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { PricingOverlay } from './pricing-overlay.js';
2
+ export interface OpenRouterResult {
3
+ overlay: PricingOverlay;
4
+ /** Ids that were skipped, and the reason, so nothing disappears silently. */
5
+ skipped: Array<{
6
+ id: string;
7
+ reason: string;
8
+ }>;
9
+ }
10
+ /**
11
+ * Turns OpenRouter's catalogue into an overlay.
12
+ *
13
+ * `knownIds` is the set the bundled catalogue already has. For those, only the
14
+ * three things OpenRouter actually knows are overridden — price in, price out,
15
+ * context window. `cacheMinTokens`, `caching`, `capability` and `tier` are left
16
+ * alone, because the bundled entry was written by somebody who looked them up
17
+ * and this feed has nothing to say about them. Overwriting a researched fact
18
+ * with a blank is not a refresh.
19
+ */
20
+ export declare function openrouterOverlay(payload: unknown, options: {
21
+ knownIds: ReadonlySet<string>;
22
+ lastReviewed: string;
23
+ idFor?: (id: string) => string;
24
+ }): OpenRouterResult;
25
+ //# sourceMappingURL=openrouter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openrouter.d.ts","sourceRoot":"","sources":["../src/openrouter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAsC3D,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,cAAc,CAAC;IACxB,6EAA6E;IAC7E,OAAO,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAgBD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE;IAAE,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAA;CAAE,GAC/F,gBAAgB,CAqDlB"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * OpenRouter quotes USD **per token**, as a decimal string.
3
+ *
4
+ * `"0.000003"` is three dollars per million. Parsed rather than multiplied
5
+ * blindly: a free model quotes `"0"`, and a model with no price for one half
6
+ * quotes `"-1"` on occasion. Neither is a price Trazum can put in a budget.
7
+ */
8
+ function perMillion(raw) {
9
+ if (typeof raw !== 'string' && typeof raw !== 'number')
10
+ return null;
11
+ const value = Number(raw);
12
+ if (!Number.isFinite(value) || value <= 0)
13
+ return null;
14
+ return value * 1_000_000;
15
+ }
16
+ /**
17
+ * Turns OpenRouter's catalogue into an overlay.
18
+ *
19
+ * `knownIds` is the set the bundled catalogue already has. For those, only the
20
+ * three things OpenRouter actually knows are overridden — price in, price out,
21
+ * context window. `cacheMinTokens`, `caching`, `capability` and `tier` are left
22
+ * alone, because the bundled entry was written by somebody who looked them up
23
+ * and this feed has nothing to say about them. Overwriting a researched fact
24
+ * with a blank is not a refresh.
25
+ */
26
+ export function openrouterOverlay(payload, options) {
27
+ const { knownIds, lastReviewed, idFor = (id) => id } = options;
28
+ const data = payload?.data;
29
+ if (!Array.isArray(data)) {
30
+ throw new Error('OpenRouter payload has no "data" array — is this the models endpoint?');
31
+ }
32
+ const models = {};
33
+ const skipped = [];
34
+ for (const entry of data) {
35
+ const rawId = typeof entry?.id === 'string' ? entry.id : '';
36
+ if (!rawId)
37
+ continue;
38
+ const id = idFor(rawId);
39
+ const input = perMillion(entry.pricing?.prompt);
40
+ const output = perMillion(entry.pricing?.completion);
41
+ if (input === null || output === null) {
42
+ // Free models and half-priced entries. Skipped rather than recorded at
43
+ // zero: a zero price makes every saving Trazum computes zero too, and a
44
+ // report full of $0.00 reads as "nothing to gain here" rather than as
45
+ // "this catalogue has no price for that".
46
+ skipped.push({ id: rawId, reason: 'no usable price' });
47
+ continue;
48
+ }
49
+ const context = Number(entry.context_length);
50
+ if (!Number.isInteger(context) || context <= 0) {
51
+ skipped.push({ id: rawId, reason: 'no context window' });
52
+ continue;
53
+ }
54
+ if (knownIds.has(id)) {
55
+ models[id] = { inputPerMTok: input, outputPerMTok: output, contextWindow: context };
56
+ continue;
57
+ }
58
+ models[id] = {
59
+ displayName: typeof entry.name === 'string' && entry.name ? entry.name : rawId,
60
+ inputPerMTok: input,
61
+ outputPerMTok: output,
62
+ contextWindow: context,
63
+ // The two the feed cannot answer. See the note at the top of this file.
64
+ cacheMinTokens: null,
65
+ caching: 'unknown',
66
+ capability: 'unknown',
67
+ tier: 'unknown',
68
+ };
69
+ }
70
+ return { overlay: { lastReviewed, models }, skipped };
71
+ }
72
+ //# sourceMappingURL=openrouter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openrouter.js","sourceRoot":"","sources":["../src/openrouter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { PricingCatalogue } from './pricing.js';
2
+ import { join, segment } from './segment.js';
3
+ import type { AsyncTokenCounter } from './tokenizer.js';
4
+ import type { OptimizationResult, OptimizeOptions, Segment, UsageProfile } from './types.js';
5
+ export declare const DEFAULT_USAGE: UsageProfile;
6
+ interface Masked {
7
+ text: string;
8
+ /** Original text of each protected segment, indexed by its mask. */
9
+ vault: string[];
10
+ }
11
+ /**
12
+ * Replaces every protected segment with a 3-character marker from the Unicode
13
+ * private use area. The rules work on the masked text, so none of them can
14
+ * touch code, URLs or template placeholders.
15
+ */
16
+ declare function mask(segments: Segment[]): Masked;
17
+ /** Puts the protected content back. */
18
+ declare function unmask(text: string, vault: string[]): string;
19
+ /**
20
+ * Optimises a prompt by applying deterministic rules.
21
+ *
22
+ * The result is reproducible: the same input always yields the same output and
23
+ * running it costs nothing. The optional LLM pass lives separately, in
24
+ * `refineWithLlm`.
25
+ */
26
+ export declare function optimize(prompt: string, options?: OptimizeOptions): OptimizationResult;
27
+ /**
28
+ * Recomputes the report with an exact token counter (e.g. the official
29
+ * token-counting endpoint).
30
+ *
31
+ * Only the headline numbers and the saving are recomputed. Per-rule token
32
+ * attribution stays on the heuristic estimate: asking a remote counter once
33
+ * per rule would multiply the calls without changing any decision.
34
+ */
35
+ export declare function withExactTokenCounts(result: OptimizationResult, counter: AsyncTokenCounter, pricing?: PricingCatalogue): Promise<OptimizationResult>;
36
+ /** Re-exports of internals useful to anyone extending the library. */
37
+ export { join, mask as maskProtected, segment, unmask as unmaskProtected };
38
+ //# sourceMappingURL=optimize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optimize.d.ts","sourceRoot":"","sources":["../src/optimize.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EACV,kBAAkB,EAClB,eAAe,EAEf,OAAO,EAEP,YAAY,EACb,MAAM,YAAY,CAAC;AAQpB,eAAO,MAAM,aAAa,EAAE,YAM3B,CAAC;AAEF,UAAU,MAAM;IACd,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;GAIG;AACH,iBAAS,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAqBzC;AAED,uCAAuC;AACvC,iBAAS,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CASrD;AAOD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,kBAAkB,CA2E1F;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,kBAAkB,EAC1B,OAAO,EAAE,iBAAiB,EAC1B,OAAO,GAAE,gBAAoC,GAC5C,OAAO,CAAC,kBAAkB,CAAC,CA2C7B;AAED,sEAAsE;AACtE,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,aAAa,EAAE,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,CAAC"}