llm-entropy-filter 1.0.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.
package/LICENSE ADDED
@@ -0,0 +1,93 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Copyright 2026 Ernesto Rosati
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
18
+
19
+ --------------------------------------------------------------------
20
+
21
+ Apache License
22
+ Version 2.0, January 2004
23
+
24
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
25
+
26
+ 1. Definitions.
27
+
28
+ "License" shall mean the terms and conditions for use, reproduction,
29
+ and distribution as defined by Sections 1 through 9 of this document.
30
+
31
+ "Licensor" shall mean the copyright owner or entity authorized by
32
+ the copyright owner that is granting the License.
33
+
34
+ "Legal Entity" shall mean the union of the acting entity and all
35
+ other entities that control, are controlled by, or are under common
36
+ control with that entity.
37
+
38
+ "You" (or "Your") shall mean an individual or Legal Entity
39
+ exercising permissions granted by this License.
40
+
41
+ "Source" form shall mean the preferred form for making modifications,
42
+ including but not limited to software source code.
43
+
44
+ "Object" form shall mean any form resulting from mechanical
45
+ transformation or translation of a Source form.
46
+
47
+ "Work" shall mean the work of authorship made available under the License.
48
+
49
+ "Derivative Works" shall mean any work based upon the Work.
50
+
51
+ 2. Grant of Copyright License.
52
+
53
+ Subject to the terms and conditions of this License, each Contributor
54
+ hereby grants You a perpetual, worldwide, non-exclusive, no-charge,
55
+ royalty-free, irrevocable copyright license to reproduce, prepare
56
+ Derivative Works of, publicly display, publicly perform, sublicense,
57
+ and distribute the Work.
58
+
59
+ 3. Grant of Patent License.
60
+
61
+ Each Contributor hereby grants You a perpetual, worldwide,
62
+ non-exclusive, no-charge, royalty-free, irrevocable patent license
63
+ to make, use, sell, offer to sell, import, and otherwise transfer
64
+ the Work.
65
+
66
+ 4. Redistribution.
67
+
68
+ You may reproduce and distribute copies of the Work provided that
69
+ You give recipients a copy of this License and retain attribution notices.
70
+
71
+ 5. Submission of Contributions.
72
+
73
+ Unless You explicitly state otherwise, any Contribution intentionally
74
+ submitted for inclusion in the Work shall be under the terms of this License.
75
+
76
+ 6. Trademarks.
77
+
78
+ This License does not grant permission to use the trade names,
79
+ trademarks, service marks, or product names of the Licensor.
80
+
81
+ 7. Disclaimer of Warranty.
82
+
83
+ The Work is provided "AS IS", WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND.
84
+
85
+ 8. Limitation of Liability.
86
+
87
+ In no event shall any Contributor be liable for damages arising from
88
+ the use of the Work.
89
+
90
+ 9. Accepting Warranty or Additional Liability.
91
+
92
+ You may offer support or warranty protection for a fee, but only
93
+ on Your own behalf and responsibility.
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ README.md reescrito (v1.0.0 listo para publicar)
2
+
3
+ Basado en tu README actual
4
+
5
+ README (5)
6
+
7
+ , aquí va una versión limpia, con tus métricas reales, endpoints, bench y dataset.
8
+
9
+ Copia y pega esto como README.md:
10
+
11
+ # llm-entropy-filter
12
+
13
+ Minimal, fast **entropy + intent gate** for LLM inputs.
14
+
15
+ This package runs a **local, deterministic heuristic gate** to detect high-entropy / low-signal inputs (spam, coercion, vague conspiracies, pseudo-science, truth relativism, broken causality) and returns an **ALLOW / WARN / BLOCK** verdict.
16
+
17
+ Use it **before** calling an LLM to reduce hallucinations, cost, and risk.
18
+
19
+ ---
20
+
21
+ ## What you get
22
+
23
+ ### Core (library)
24
+ - `gate(text)` → `{ action, entropy_score, flags, intention, confidence, rationale }`
25
+ - `gateLLM(text)` → alias of `gate(text)` (kept for compatibility)
26
+ - `runEntropyFilter(text)` → underlying entropy + intention analysis utilities
27
+
28
+ ### Demo (server)
29
+ - `POST /analyze` → runs local `gate(text)` + returns `meta.ts` + `meta.version`
30
+ - `POST /triad` → optional OpenAI analysis (only if `OPENAI_API_KEY` is set)
31
+
32
+ ---
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ npm i llm-entropy-filter
38
+
39
+ Quickstart (library)
40
+ import { gate } from "llm-entropy-filter";
41
+
42
+ const r = gate("¡¡COMPRA YA!! Oferta limitada 90% OFF $$$");
43
+ console.log(r);
44
+
45
+
46
+ Example output:
47
+
48
+ {
49
+ "action": "BLOCK",
50
+ "entropy_score": 0.7,
51
+ "flags": ["urgency","spam_sales","money_signal","shouting"],
52
+ "intention": "marketing_spam",
53
+ "confidence": 0.85,
54
+ "rationale": "Detecté señales de venta agresiva/urgencia/dinero."
55
+ }
56
+
57
+ Demo server (Express)
58
+
59
+ Start:
60
+
61
+ npm run serve
62
+
63
+
64
+ Health:
65
+
66
+ curl -s http://127.0.0.1:3000/health
67
+
68
+
69
+ Local gate:
70
+
71
+ curl -s -X POST http://127.0.0.1:3000/analyze \
72
+ -H "Content-Type: application/json" \
73
+ -d '{"text":"Congratulations! You won a FREE iPhone. Click here to claim now!"}' | jq .
74
+
75
+
76
+ You will also see:
77
+
78
+ "meta": { "ts": 1769546511060, "version": "1.0.0" }
79
+
80
+ Optional: OpenAI triad demo
81
+ export OPENAI_API_KEY="YOUR_KEY"
82
+ export OPENAI_MODEL="gpt-4.1-mini"
83
+
84
+ curl -s -X POST http://127.0.0.1:3000/triad \
85
+ -H "Content-Type: application/json" \
86
+ -d '{"text":"Vivimos en una simulación y todos lo esconden."}' | jq .
87
+
88
+
89
+ /triad is a demo layer. The product is the local gate().
90
+
91
+ Benchmarks (measured)
92
+ HTTP gate /analyze (local, deterministic)
93
+
94
+ Command:
95
+
96
+ npx autocannon -m POST -c 30 -d 10 --renderStatusCodes \
97
+ http://127.0.0.1:3000/analyze \
98
+ -H "Content-Type: application/json" \
99
+ -b '{"text":"Congratulations. You won a FREE iPhone. Click here to claim now."}'
100
+
101
+
102
+ Observed (typical run):
103
+
104
+ ~5.2k req/s
105
+
106
+ ~5.1 ms avg latency (p50 ~4 ms)
107
+
108
+ LLM demo /triad (OpenAI)
109
+
110
+ Command:
111
+
112
+ npx autocannon -m POST -c 2 -d 30 --renderStatusCodes \
113
+ http://127.0.0.1:3000/triad \
114
+ -H "Content-Type: application/json" \
115
+ -b '{"text":"Texto real de prueba (1-3 párrafos) ..."}'
116
+
117
+
118
+ Observed (typical run):
119
+
120
+ ~1.7 req/s
121
+
122
+ ~1.17 s avg latency
123
+
124
+ Dataset mini + bench script (no HTTP)
125
+
126
+ A tiny CSV lives at:
127
+
128
+ bench/sms_spam.csv
129
+
130
+ Run the bench:
131
+
132
+ node bench/sms_spam_bench.mjs bench/sms_spam.csv
133
+ cat bench/reports/sms_spam_report.md
134
+
135
+
136
+ Typical report:
137
+
138
+ Throughput: ~9–10k samples/sec
139
+
140
+ Actions: ALLOW / WARN / BLOCK distribution
141
+
142
+ Confusion table (ground truth spam/ham → action)
143
+
144
+ Top flags + intentions
145
+
146
+ JSON + Markdown reports written to bench/reports/
147
+
148
+ Design goals
149
+
150
+ Fast: pure heuristics, no network calls
151
+
152
+ Portable: works in any Node environment
153
+
154
+ Composable: middleware/wrapper before calling an LLM
155
+
156
+ Transparent: flags explain why an input is risky
157
+
158
+ Observable: /analyze returns meta.ts and meta.version
159
+
160
+ Roadmap
161
+
162
+ Expand multilingual spam patterns
163
+
164
+ Optional suggested_rewrite to lower entropy
165
+
166
+ Example integrations: Next.js / Vercel, Express, Cloudflare Workers
167
+
168
+ Extended dataset benches + cost-savings estimates
169
+
170
+ License
171
+
172
+ Apache-2.0
173
+
174
+ Copyright (c) 2026 Ernesto Rosati
175
+
176
+
177
+ ---
178
+
package/dist/index.cjs ADDED
@@ -0,0 +1,265 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ analyzeEntropy: () => analyzeEntropy,
24
+ entropyMiddleware: () => entropyMiddleware,
25
+ evaluateIntention: () => evaluateIntention,
26
+ gate: () => gateLLM,
27
+ gateLLM: () => gateLLM,
28
+ runEntropyFilter: () => runEntropyFilter
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+
32
+ // src/entropy.ts
33
+ function clamp01(x) {
34
+ return Math.max(0, Math.min(1, x));
35
+ }
36
+ function countRegex(text, re) {
37
+ const m = text.match(re);
38
+ return m ? m.length : 0;
39
+ }
40
+ function analyzeEntropy(text) {
41
+ const raw = text || "";
42
+ const t = raw.toLowerCase();
43
+ const flags = [];
44
+ let score = 0;
45
+ if (/\b(ahora|ya|urgente|última|hoy|inmediato)\b/.test(t)) {
46
+ flags.push("urgency");
47
+ score += 0.2;
48
+ }
49
+ if (/\b(compra|oferta|promo|descuento|gratis|clic|click|off)\b/.test(t)) {
50
+ flags.push("spam_sales");
51
+ score += 0.25;
52
+ }
53
+ const moneyHits = countRegex(raw, /\$+/g);
54
+ if (moneyHits > 0) {
55
+ flags.push("money_signal");
56
+ score += Math.min(0.2, moneyHits * 0.05);
57
+ }
58
+ const exclam = countRegex(raw, /!/g);
59
+ const capsRatio = (() => {
60
+ const letters = raw.match(/[A-Za-zÁÉÍÓÚÜÑáéíóúüñ]/g) || [];
61
+ if (letters.length === 0) return 0;
62
+ const caps = (raw.match(/[A-ZÁÉÍÓÚÜÑ]/g) || []).length;
63
+ return caps / letters.length;
64
+ })();
65
+ if (exclam >= 3 || capsRatio >= 0.35) {
66
+ flags.push("shouting");
67
+ score += 0.2;
68
+ }
69
+ if (/\b(si de verdad|si me quisieras|es tu culpa|no tienes opción|me debes)\b/.test(t)) {
70
+ flags.push("emotional_manipulation");
71
+ score += 0.35;
72
+ }
73
+ if (/\b(todos lo saben|lo esconden|la verdad oculta|ellos no quieren|simulación)\b/.test(t)) {
74
+ flags.push("conspiracy_vague");
75
+ score += 0.2;
76
+ }
77
+ if (/\b(la cultura lo prueba|es obvio|todo mundo sabe|se sabe|está claro)\b/.test(t)) {
78
+ flags.push("weak_evidence");
79
+ score += 0.2;
80
+ }
81
+ if (/\b(ellos|la élite|los de arriba)\b/.test(t) && /\b(esconden|ocultan|tapan)\b/.test(t)) {
82
+ flags.push("hidden_actor");
83
+ score += 0.15;
84
+ }
85
+ if (/\b(física cuántica|cuantica|cuántico|quantum)\b/.test(t)) {
86
+ flags.push("pseudo_science_quantum");
87
+ score += 0.2;
88
+ }
89
+ const manifestHits = countRegex(t, /\b(manifestar|manifestación|decretar|decreto|vibración|vibracion|frecuencia|energía|energia|ley de la atracción|universo me lo dará)\b/g) + countRegex(t, /\b(realine(a|ar)\b.*\bátom|\bátom|\batomos\b)/g);
90
+ if (manifestHits > 0) {
91
+ flags.push("magic_manifesting");
92
+ score += Math.min(0.35, 0.15 + manifestHits * 0.06);
93
+ }
94
+ if (/\b(no hay una verdad objetiva|no existe la verdad objetiva|tu verdad|mi verdad|la verdad es relativa|lo que importa es lo que sientes)\b/.test(t)) {
95
+ flags.push("truth_relativism");
96
+ score += 0.35;
97
+ }
98
+ if (/\b(deben|debe)\b.*\b(obligatoriamente|por lo tanto|por ende)\b/.test(t) || /\b(la materia)\b.*\b(se subordina|obedece)\b/.test(t)) {
99
+ flags.push("broken_causality");
100
+ score += 0.2;
101
+ }
102
+ score = clamp01(score);
103
+ return { score, flags };
104
+ }
105
+
106
+ // src/intention.ts
107
+ function clamp012(x) {
108
+ return Math.max(0, Math.min(1, x));
109
+ }
110
+ function evaluateIntention(text) {
111
+ const raw = text || "";
112
+ const t = raw.toLowerCase();
113
+ const isHelp = /\b(ayuda|ayúdame|explica|resume|resumir|cómo|como|puedes|podrías|por favor)\b/.test(
114
+ t
115
+ );
116
+ const isSpam = /\b(compra|oferta|promo|descuento|gratis|haz clic|click|off|90%|% off)\b/.test(
117
+ t
118
+ ) || (raw.match(/\$+/g) || []).length > 0;
119
+ const isManip = /\b(si de verdad|si me quisieras|es tu culpa|no tienes opción|me debes)\b/.test(
120
+ t
121
+ );
122
+ const isConsp = /\b(simulación|todos lo saben|lo esconden|verdad oculta|ellos no quieren)\b/.test(
123
+ t
124
+ );
125
+ const isMisinformation = /\b(física cuántica|cuantica|cuántica|cuántico|quantum)\b/.test(t) || /\b(manifestar|manifestación|decretar|decreto|vibración|vibracion|frecuencia|energía|energia|ley de la atracción)\b/.test(t) || /\b(no hay una verdad objetiva|no existe la verdad objetiva|tu verdad|mi verdad|la verdad es relativa)\b/.test(t) || /\b(la materia)\b.*\b(se subordina|obedece)\b/.test(t);
126
+ let intention = "unknown";
127
+ let confidence = 0;
128
+ let rationale = "";
129
+ if (isSpam) {
130
+ intention = "marketing_spam";
131
+ confidence = 0.85;
132
+ rationale = "Detect\xE9 se\xF1ales de venta agresiva/urgencia/dinero.";
133
+ } else if (isManip) {
134
+ intention = "manipulation";
135
+ confidence = 0.85;
136
+ rationale = "Detect\xE9 coerci\xF3n/culpa/chantaje emocional.";
137
+ } else if (isConsp) {
138
+ intention = "conspiracy";
139
+ confidence = 0.75;
140
+ rationale = "Detect\xE9 marco conspirativo vago ('lo esconden', 'todos lo saben').";
141
+ } else if (isMisinformation) {
142
+ intention = "misinformation";
143
+ confidence = 0.85;
144
+ rationale = "Detect\xE9 patr\xF3n de pseudo-ciencia/pensamiento m\xE1gico/relativismo de la verdad (alta probabilidad de desinformaci\xF3n).";
145
+ } else if (isHelp) {
146
+ intention = "request_help";
147
+ confidence = 0.7;
148
+ rationale = "Parece una petici\xF3n leg\xEDtima de ayuda/explicaci\xF3n.";
149
+ }
150
+ return { intention, confidence: clamp012(confidence), rationale };
151
+ }
152
+
153
+ // src/wrapper.ts
154
+ function runEntropyFilter(text) {
155
+ const entropy_analysis = analyzeEntropy(text);
156
+ let intention_evaluation = evaluateIntention(text);
157
+ const hardFlags = new Set(entropy_analysis.flags);
158
+ const epistemicEntropy = hardFlags.has("truth_relativism") || hardFlags.has("magic_manifesting") || hardFlags.has("pseudo_science_quantum") || hardFlags.has("broken_causality");
159
+ if (epistemicEntropy) {
160
+ intention_evaluation = {
161
+ intention: "misinformation",
162
+ confidence: Math.max(intention_evaluation.confidence ?? 0.7, 0.8),
163
+ rationale: "Detect\xE9 patr\xF3n de pseudo-ciencia/pensamiento m\xE1gico/relativismo de la verdad; alta probabilidad de desinformaci\xF3n o argumento sin anclaje causal."
164
+ };
165
+ }
166
+ return { entropy_analysis, intention_evaluation };
167
+ }
168
+
169
+ // src/middleware.ts
170
+ function entropyMiddleware(req, _res, next) {
171
+ const text = req?.body?.text;
172
+ const result = typeof text === "string" ? runEntropyFilter(text) : runEntropyFilter("");
173
+ req.entropy = result;
174
+ next?.();
175
+ }
176
+
177
+ // src/gate.ts
178
+ function clamp013(x) {
179
+ return Math.max(0, Math.min(1, x));
180
+ }
181
+ function mergeFlags(base, extra) {
182
+ const set = new Set(base);
183
+ const out = [...base];
184
+ for (const f of extra) {
185
+ if (!set.has(f)) {
186
+ set.add(f);
187
+ out.push(f);
188
+ }
189
+ }
190
+ return out;
191
+ }
192
+ function englishSpamBooster(rawText) {
193
+ const t = (rawText || "").toLowerCase();
194
+ const patterns = [
195
+ { re: /\bfree\b/g, score: 0.08, flag: "spam_kw_free" },
196
+ { re: /\bwinner\b|\bwon\b|\bcongratulations\b/g, score: 0.1, flag: "spam_kw_winner" },
197
+ { re: /\bclaim\b|\bredeem\b/g, score: 0.08, flag: "spam_kw_claim" },
198
+ { re: /\bclick\b|\bclick here\b|\bopen link\b|\btap here\b/g, score: 0.1, flag: "spam_kw_click" },
199
+ { re: /\blimited time\b|\bact now\b|\bfinal notice\b|\bbefore midnight\b/g, score: 0.1, flag: "spam_kw_urgency_en" },
200
+ { re: /\bverify\b|\bconfirm\b|\baccount\b.*\b(suspended|locked)\b/g, score: 0.12, flag: "spam_kw_verify" },
201
+ { re: /\bprize\b|\bgift card\b|\bgiftcard\b|\bvoucher\b|\biphone\b|\bsurvey\b/g, score: 0.1, flag: "spam_kw_prize" },
202
+ { re: /\bloan\b|\bpre-?approved\b|\bno credit check\b/g, score: 0.12, flag: "spam_kw_loan" },
203
+ { re: /\bcrypto\b|\bairdrop\b|\bwallet\b|\bseed phrase\b/g, score: 0.1, flag: "spam_kw_crypto" },
204
+ { re: /\bdelivery failed\b|\breschedule\b|\bpackage\b|\bcourier\b/g, score: 0.1, flag: "spam_kw_delivery" },
205
+ { re: /\btax refund\b|\bunpaid\b|\brefund\b|\bchargeback\b/g, score: 0.1, flag: "spam_kw_refund" }
206
+ ];
207
+ let addScore = 0;
208
+ const addFlags = [];
209
+ let hitCount = 0;
210
+ for (const p of patterns) {
211
+ const m = t.match(p.re);
212
+ if (m && m.length > 0) {
213
+ hitCount += m.length;
214
+ addScore += p.score;
215
+ addFlags.push(p.flag);
216
+ }
217
+ }
218
+ addScore = Math.min(0.35, addScore);
219
+ if (hitCount > 0) addFlags.push("spam_keywords_en");
220
+ return { addScore, addFlags, hitCount };
221
+ }
222
+ function gateLLM(text, config = {}) {
223
+ const warnT = config.warnThreshold ?? 0.25;
224
+ const blockT = config.blockThreshold ?? 0.6;
225
+ const r = runEntropyFilter(text);
226
+ let score = r.entropy_analysis.score;
227
+ let flags = [...r.entropy_analysis.flags];
228
+ const booster = englishSpamBooster(text);
229
+ if (booster.hitCount > 0) {
230
+ score = clamp013(score + booster.addScore);
231
+ flags = mergeFlags(flags, booster.addFlags);
232
+ }
233
+ let intention = r.intention_evaluation.intention || "unknown";
234
+ let confidence = r.intention_evaluation.confidence ?? 0;
235
+ let rationale = r.intention_evaluation.rationale ?? "";
236
+ if (booster.hitCount > 0 && intention === "unknown") {
237
+ intention = "marketing_spam";
238
+ confidence = Math.max(confidence, 0.75);
239
+ rationale = (rationale ? rationale + " " : "") + "Detect\xE9 keywords t\xEDpicas de spam/phishing en ingl\xE9s.";
240
+ }
241
+ const hasSpam = flags.includes("spam_sales") || flags.includes("spam_keywords_en");
242
+ const hasMoneySignals = flags.includes("money_signal") || flags.includes("spam_kw_prize") || flags.includes("spam_kw_loan");
243
+ const strongSpam = hasSpam && hasMoneySignals;
244
+ let action = "ALLOW";
245
+ if (score > blockT || strongSpam) action = "BLOCK";
246
+ else if (score >= warnT) action = "WARN";
247
+ return {
248
+ action,
249
+ entropy_score: score,
250
+ flags,
251
+ intention,
252
+ confidence: clamp013(confidence),
253
+ rationale
254
+ };
255
+ }
256
+ // Annotate the CommonJS export names for ESM import in node:
257
+ 0 && (module.exports = {
258
+ analyzeEntropy,
259
+ entropyMiddleware,
260
+ evaluateIntention,
261
+ gate,
262
+ gateLLM,
263
+ runEntropyFilter
264
+ });
265
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/entropy.ts","../src/intention.ts","../src/wrapper.ts","../src/middleware.ts","../src/gate.ts"],"sourcesContent":["// src/index.ts\nexport { analyzeEntropy } from \"./entropy\";\nexport { evaluateIntention } from \"./intention\";\nexport { runEntropyFilter } from \"./wrapper\";\nexport { entropyMiddleware } from \"./middleware\";\n\nexport type {\n EntropyResult,\n IntentionEvaluation,\n FilterResult\n} from \"./types\";\nexport { gateLLM as gate } from \"./gate\";\nexport * from \"./gate\";\n","// src/entropy.ts\nexport type EntropyResult = {\n score: number; // 0..1\n flags: string[];\n};\n\nfunction clamp01(x: number) {\n return Math.max(0, Math.min(1, x));\n}\n\nfunction countRegex(text: string, re: RegExp) {\n const m = text.match(re);\n return m ? m.length : 0;\n}\n\nexport function analyzeEntropy(text: string): EntropyResult {\n const raw = text || \"\";\n const t = raw.toLowerCase();\n const flags: string[] = [];\n let score = 0;\n\n // 1) Urgencia / presión\n if (/\\b(ahora|ya|urgente|última|hoy|inmediato)\\b/.test(t)) {\n flags.push(\"urgency\");\n score += 0.20;\n }\n\n // 2) Spam / venta agresiva\n if (/\\b(compra|oferta|promo|descuento|gratis|clic|click|off)\\b/.test(t)) {\n flags.push(\"spam_sales\");\n score += 0.25;\n }\n\n // 3) Señales $$$ / símbolos\n const moneyHits = countRegex(raw, /\\$+/g);\n if (moneyHits > 0) {\n flags.push(\"money_signal\");\n score += Math.min(0.20, moneyHits * 0.05);\n }\n\n // 4) Exceso de signos / gritos\n const exclam = countRegex(raw, /!/g);\n const capsRatio = (() => {\n const letters = raw.match(/[A-Za-zÁÉÍÓÚÜÑáéíóúüñ]/g) || [];\n if (letters.length === 0) return 0;\n const caps = (raw.match(/[A-ZÁÉÍÓÚÜÑ]/g) || []).length;\n return caps / letters.length;\n })();\n\n if (exclam >= 3 || capsRatio >= 0.35) {\n flags.push(\"shouting\");\n score += 0.20;\n }\n\n // 5) Manipulación / culpa / coerción\n if (/\\b(si de verdad|si me quisieras|es tu culpa|no tienes opción|me debes)\\b/.test(t)) {\n flags.push(\"emotional_manipulation\");\n score += 0.35;\n }\n\n // 6) Conspiración vaga / “todos lo saben”\n if (/\\b(todos lo saben|lo esconden|la verdad oculta|ellos no quieren|simulación)\\b/.test(t)) {\n flags.push(\"conspiracy_vague\");\n score += 0.20;\n }\n\n // 6.5) “Prueba vaga” / apelación a cultura como evidencia\n if (/\\b(la cultura lo prueba|es obvio|todo mundo sabe|se sabe|está claro)\\b/.test(t)) {\n flags.push(\"weak_evidence\");\n score += 0.20;\n }\n\n // 6.6) Totalización + agente oculto (“ellos”)\n if (/\\b(ellos|la élite|los de arriba)\\b/.test(t) && /\\b(esconden|ocultan|tapan)\\b/.test(t)) {\n flags.push(\"hidden_actor\");\n score += 0.15;\n }\n\n // ------------------------------------------------------------\n // NUEVO: Entropía por pseudo-ciencia / pensamiento mágico / relativismo\n // ------------------------------------------------------------\n\n // 7) Pseudo-ciencia \"cuántica\" usada como licencia mágica\n if (/\\b(física cuántica|cuantica|cuántico|quantum)\\b/.test(t)) {\n flags.push(\"pseudo_science_quantum\");\n score += 0.20;\n }\n\n // 8) Manifestación mágica / decretos / vibración / energía como causalidad\n const manifestHits =\n countRegex(t, /\\b(manifestar|manifestación|decretar|decreto|vibración|vibracion|frecuencia|energía|energia|ley de la atracción|universo me lo dará)\\b/g) +\n countRegex(t, /\\b(realine(a|ar)\\b.*\\bátom|\\bátom|\\batomos\\b)/g);\n\n if (manifestHits > 0) {\n flags.push(\"magic_manifesting\");\n score += Math.min(0.35, 0.15 + manifestHits * 0.06);\n }\n\n // 9) Relativismo de la verdad / negación explícita de verdad objetiva\n if (/\\b(no hay una verdad objetiva|no existe la verdad objetiva|tu verdad|mi verdad|la verdad es relativa|lo que importa es lo que sientes)\\b/.test(t)) {\n flags.push(\"truth_relativism\");\n score += 0.35;\n }\n\n // 10) Causalidad rota / obligación metafísica (\"debe\" porque lo deseo)\n if (/\\b(deben|debe)\\b.*\\b(obligatoriamente|por lo tanto|por ende)\\b/.test(t) || /\\b(la materia)\\b.*\\b(se subordina|obedece)\\b/.test(t)) {\n flags.push(\"broken_causality\");\n score += 0.20;\n }\n\n // Normaliza: score final 0..1\n score = clamp01(score);\n\n return { score, flags };\n}\n","// src/intention.ts\nimport type { IntentionEvaluation, IntentionType } from \"./types\";\n\nfunction clamp01(x: number) {\n return Math.max(0, Math.min(1, x));\n}\n\nexport function evaluateIntention(text: string): IntentionEvaluation {\n const raw = text || \"\";\n const t = raw.toLowerCase();\n\n // Heurísticas rápidas (MVP)\n const isHelp =\n /\\b(ayuda|ayúdame|explica|resume|resumir|cómo|como|puedes|podrías|por favor)\\b/.test(\n t\n );\n\n const isSpam =\n /\\b(compra|oferta|promo|descuento|gratis|haz clic|click|off|90%|% off)\\b/.test(\n t\n ) || (raw.match(/\\$+/g) || []).length > 0;\n\n const isManip =\n /\\b(si de verdad|si me quisieras|es tu culpa|no tienes opción|me debes)\\b/.test(\n t\n );\n\n const isConsp =\n /\\b(simulación|todos lo saben|lo esconden|verdad oculta|ellos no quieren)\\b/.test(\n t\n );\n\n // NUEVO: pseudo-ciencia / pensamiento mágico / relativismo (desinformación)\n const isMisinformation =\n /\\b(física cuántica|cuantica|cuántica|cuántico|quantum)\\b/.test(t) ||\n /\\b(manifestar|manifestación|decretar|decreto|vibración|vibracion|frecuencia|energía|energia|ley de la atracción)\\b/.test(t) ||\n /\\b(no hay una verdad objetiva|no existe la verdad objetiva|tu verdad|mi verdad|la verdad es relativa)\\b/.test(t) ||\n /\\b(la materia)\\b.*\\b(se subordina|obedece)\\b/.test(t);\n\n let intention: IntentionType = \"unknown\";\n let confidence = 0.0;\n let rationale = \"\";\n\n if (isSpam) {\n intention = \"marketing_spam\";\n confidence = 0.85;\n rationale = \"Detecté señales de venta agresiva/urgencia/dinero.\";\n } else if (isManip) {\n intention = \"manipulation\";\n confidence = 0.85;\n rationale = \"Detecté coerción/culpa/chantaje emocional.\";\n } else if (isConsp) {\n intention = \"conspiracy\";\n confidence = 0.75;\n rationale = \"Detecté marco conspirativo vago ('lo esconden', 'todos lo saben').\";\n } else if (isMisinformation) {\n intention = \"misinformation\";\n confidence = 0.85;\n rationale =\n \"Detecté patrón de pseudo-ciencia/pensamiento mágico/relativismo de la verdad (alta probabilidad de desinformación).\";\n } else if (isHelp) {\n intention = \"request_help\";\n confidence = 0.7;\n rationale = \"Parece una petición legítima de ayuda/explicación.\";\n }\n\n return { intention, confidence: clamp01(confidence), rationale };\n}\n","// src/wrapper.ts\nimport { analyzeEntropy } from \"./entropy\";\nimport { evaluateIntention } from \"./intention\";\nimport type { FilterResult, IntentionEvaluation } from \"./types\";\n\nexport function runEntropyFilter(text: string): FilterResult {\n const entropy_analysis = analyzeEntropy(text);\n let intention_evaluation: IntentionEvaluation = evaluateIntention(text);\n\n // Corrección: si el texto trae señales fuertes de entropía epistemológica,\n // no lo clasifiques como \"request_help\" solo por ser pregunta larga.\n const hardFlags = new Set(entropy_analysis.flags);\n const epistemicEntropy =\n hardFlags.has(\"truth_relativism\") ||\n hardFlags.has(\"magic_manifesting\") ||\n hardFlags.has(\"pseudo_science_quantum\") ||\n hardFlags.has(\"broken_causality\");\n\n if (epistemicEntropy) {\n intention_evaluation = {\n intention: \"misinformation\",\n confidence: Math.max(intention_evaluation.confidence ?? 0.7, 0.8),\n rationale:\n \"Detecté patrón de pseudo-ciencia/pensamiento mágico/relativismo de la verdad; alta probabilidad de desinformación o argumento sin anclaje causal.\"\n };\n }\n\n return { entropy_analysis, intention_evaluation };\n}\n","// src/middleware.ts\nimport { runEntropyFilter } from \"./wrapper\";\nimport type { FilterResult } from \"./types\";\n\n/**\n * Middleware agnóstico (NO depende de express types).\n * Compatible con Express / Next API routes / cualquier framework estilo req-res-next.\n *\n * Espera: req.body.text (string)\n * Escribe: req.entropy = FilterResult\n */\nexport function entropyMiddleware(req: any, _res: any, next: any) {\n const text = req?.body?.text;\n\n const result: FilterResult =\n typeof text === \"string\" ? runEntropyFilter(text) : runEntropyFilter(\"\");\n\n req.entropy = result;\n next?.();\n}\n\n/**\n * Tipo auxiliar opcional (sin forzar dependencias).\n * Útil si quieres tipar tu req en tu app.\n */\nexport type EntropyAugmentedRequest = {\n body?: { text?: unknown };\n entropy?: FilterResult;\n};\n","// src/gate.ts\nimport { runEntropyFilter } from \"./wrapper\";\n\nexport type GateAction = \"ALLOW\" | \"WARN\" | \"BLOCK\";\n\nexport type GateResult = {\n action: GateAction;\n entropy_score: number;\n flags: string[];\n intention: string;\n confidence: number;\n rationale: string;\n};\n\nexport type GateConfig = {\n warnThreshold?: number; // default: 0.25\n blockThreshold?: number; // default: 0.60\n};\n\n/** Clamp 0..1 */\nfunction clamp01(x: number) {\n return Math.max(0, Math.min(1, x));\n}\n\n/** Add unique flags preserving order */\nfunction mergeFlags(base: string[], extra: string[]) {\n const set = new Set(base);\n const out = [...base];\n for (const f of extra) {\n if (!set.has(f)) {\n set.add(f);\n out.push(f);\n }\n }\n return out;\n}\n\n/**\n * Detección de spam/phishing en inglés por keywords.\n * Se implementa como “booster” de score + flags (sin tocar entropy.ts).\n */\nfunction englishSpamBooster(rawText: string): {\n addScore: number;\n addFlags: string[];\n hitCount: number;\n} {\n const t = (rawText || \"\").toLowerCase();\n\n // Patrones típicos (spam / phishing / promos agresivas)\n const patterns: Array<{ re: RegExp; score: number; flag: string }> = [\n { re: /\\bfree\\b/g, score: 0.08, flag: \"spam_kw_free\" },\n { re: /\\bwinner\\b|\\bwon\\b|\\bcongratulations\\b/g, score: 0.10, flag: \"spam_kw_winner\" },\n { re: /\\bclaim\\b|\\bredeem\\b/g, score: 0.08, flag: \"spam_kw_claim\" },\n { re: /\\bclick\\b|\\bclick here\\b|\\bopen link\\b|\\btap here\\b/g, score: 0.10, flag: \"spam_kw_click\" },\n { re: /\\blimited time\\b|\\bact now\\b|\\bfinal notice\\b|\\bbefore midnight\\b/g, score: 0.10, flag: \"spam_kw_urgency_en\" },\n { re: /\\bverify\\b|\\bconfirm\\b|\\baccount\\b.*\\b(suspended|locked)\\b/g, score: 0.12, flag: \"spam_kw_verify\" },\n { re: /\\bprize\\b|\\bgift card\\b|\\bgiftcard\\b|\\bvoucher\\b|\\biphone\\b|\\bsurvey\\b/g, score: 0.10, flag: \"spam_kw_prize\" },\n { re: /\\bloan\\b|\\bpre-?approved\\b|\\bno credit check\\b/g, score: 0.12, flag: \"spam_kw_loan\" },\n { re: /\\bcrypto\\b|\\bairdrop\\b|\\bwallet\\b|\\bseed phrase\\b/g, score: 0.10, flag: \"spam_kw_crypto\" },\n { re: /\\bdelivery failed\\b|\\breschedule\\b|\\bpackage\\b|\\bcourier\\b/g, score: 0.10, flag: \"spam_kw_delivery\" },\n { re: /\\btax refund\\b|\\bunpaid\\b|\\brefund\\b|\\bchargeback\\b/g, score: 0.10, flag: \"spam_kw_refund\" },\n ];\n\n let addScore = 0;\n const addFlags: string[] = [];\n let hitCount = 0;\n\n for (const p of patterns) {\n const m = t.match(p.re);\n if (m && m.length > 0) {\n hitCount += m.length;\n addScore += p.score; // suma “por patrón”, no por ocurrencia (controlado)\n addFlags.push(p.flag);\n }\n }\n\n // cap para no sobre-castigar\n addScore = Math.min(0.35, addScore);\n\n if (hitCount > 0) addFlags.push(\"spam_keywords_en\");\n\n return { addScore, addFlags, hitCount };\n}\n\n/**\n * Gate principal (producto): deterministic + barato.\n */\nexport function gateLLM(text: string, config: GateConfig = {}): GateResult {\n const warnT = config.warnThreshold ?? 0.25;\n const blockT = config.blockThreshold ?? 0.6;\n\n const r = runEntropyFilter(text);\n\n // base\n let score = r.entropy_analysis.score;\n let flags = [...r.entropy_analysis.flags];\n\n // booster EN\n const booster = englishSpamBooster(text);\n if (booster.hitCount > 0) {\n score = clamp01(score + booster.addScore);\n flags = mergeFlags(flags, booster.addFlags);\n }\n\n // intención base (intention.ts)\n let intention = r.intention_evaluation.intention || \"unknown\";\n let confidence = r.intention_evaluation.confidence ?? 0;\n let rationale = r.intention_evaluation.rationale ?? \"\";\n\n // si pegó booster EN y quedó unknown → marketing_spam\n if (booster.hitCount > 0 && intention === \"unknown\") {\n intention = \"marketing_spam\";\n confidence = Math.max(confidence, 0.75);\n rationale = (rationale ? rationale + \" \" : \"\") + \"Detecté keywords típicas de spam/phishing en inglés.\";\n }\n\n // reglas fuertes (para BLOCK “vendible”)\n const hasSpam =\n flags.includes(\"spam_sales\") ||\n flags.includes(\"spam_keywords_en\");\n\n const hasMoneySignals =\n flags.includes(\"money_signal\") ||\n flags.includes(\"spam_kw_prize\") ||\n flags.includes(\"spam_kw_loan\");\n\n const strongSpam = hasSpam && hasMoneySignals;\n\n let action: GateAction = \"ALLOW\";\n if (score > blockT || strongSpam) action = \"BLOCK\";\n else if (score >= warnT) action = \"WARN\";\n\n return {\n action,\n entropy_score: score,\n flags,\n intention,\n confidence: clamp01(confidence),\n rationale,\n };\n}\n\n// Alias “bonito” para tu server: gate(text)\nexport const gate = gateLLM;\n"],"mappings":"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","names":["clamp01","clamp01"]}
@@ -0,0 +1,53 @@
1
+ type EntropyResult$1 = {
2
+ score: number;
3
+ flags: string[];
4
+ };
5
+ declare function analyzeEntropy(text: string): EntropyResult$1;
6
+
7
+ type EntropyResult = {
8
+ score: number;
9
+ flags: string[];
10
+ };
11
+ type IntentionType = "unknown" | "request_help" | "marketing_spam" | "manipulation" | "conspiracy" | "misinformation";
12
+ type IntentionEvaluation = {
13
+ intention: IntentionType;
14
+ confidence: number;
15
+ rationale?: string;
16
+ };
17
+ type FilterResult = {
18
+ entropy_analysis: EntropyResult;
19
+ intention_evaluation: IntentionEvaluation;
20
+ };
21
+
22
+ declare function evaluateIntention(text: string): IntentionEvaluation;
23
+
24
+ declare function runEntropyFilter(text: string): FilterResult;
25
+
26
+ /**
27
+ * Middleware agnóstico (NO depende de express types).
28
+ * Compatible con Express / Next API routes / cualquier framework estilo req-res-next.
29
+ *
30
+ * Espera: req.body.text (string)
31
+ * Escribe: req.entropy = FilterResult
32
+ */
33
+ declare function entropyMiddleware(req: any, _res: any, next: any): void;
34
+
35
+ type GateAction = "ALLOW" | "WARN" | "BLOCK";
36
+ type GateResult = {
37
+ action: GateAction;
38
+ entropy_score: number;
39
+ flags: string[];
40
+ intention: string;
41
+ confidence: number;
42
+ rationale: string;
43
+ };
44
+ type GateConfig = {
45
+ warnThreshold?: number;
46
+ blockThreshold?: number;
47
+ };
48
+ /**
49
+ * Gate principal (producto): deterministic + barato.
50
+ */
51
+ declare function gateLLM(text: string, config?: GateConfig): GateResult;
52
+
53
+ export { type EntropyResult, type FilterResult, type GateAction, type GateConfig, type GateResult, type IntentionEvaluation, analyzeEntropy, entropyMiddleware, evaluateIntention, gateLLM as gate, gateLLM, runEntropyFilter };
@@ -0,0 +1,53 @@
1
+ type EntropyResult$1 = {
2
+ score: number;
3
+ flags: string[];
4
+ };
5
+ declare function analyzeEntropy(text: string): EntropyResult$1;
6
+
7
+ type EntropyResult = {
8
+ score: number;
9
+ flags: string[];
10
+ };
11
+ type IntentionType = "unknown" | "request_help" | "marketing_spam" | "manipulation" | "conspiracy" | "misinformation";
12
+ type IntentionEvaluation = {
13
+ intention: IntentionType;
14
+ confidence: number;
15
+ rationale?: string;
16
+ };
17
+ type FilterResult = {
18
+ entropy_analysis: EntropyResult;
19
+ intention_evaluation: IntentionEvaluation;
20
+ };
21
+
22
+ declare function evaluateIntention(text: string): IntentionEvaluation;
23
+
24
+ declare function runEntropyFilter(text: string): FilterResult;
25
+
26
+ /**
27
+ * Middleware agnóstico (NO depende de express types).
28
+ * Compatible con Express / Next API routes / cualquier framework estilo req-res-next.
29
+ *
30
+ * Espera: req.body.text (string)
31
+ * Escribe: req.entropy = FilterResult
32
+ */
33
+ declare function entropyMiddleware(req: any, _res: any, next: any): void;
34
+
35
+ type GateAction = "ALLOW" | "WARN" | "BLOCK";
36
+ type GateResult = {
37
+ action: GateAction;
38
+ entropy_score: number;
39
+ flags: string[];
40
+ intention: string;
41
+ confidence: number;
42
+ rationale: string;
43
+ };
44
+ type GateConfig = {
45
+ warnThreshold?: number;
46
+ blockThreshold?: number;
47
+ };
48
+ /**
49
+ * Gate principal (producto): deterministic + barato.
50
+ */
51
+ declare function gateLLM(text: string, config?: GateConfig): GateResult;
52
+
53
+ export { type EntropyResult, type FilterResult, type GateAction, type GateConfig, type GateResult, type IntentionEvaluation, analyzeEntropy, entropyMiddleware, evaluateIntention, gateLLM as gate, gateLLM, runEntropyFilter };
package/dist/index.js ADDED
@@ -0,0 +1,233 @@
1
+ // src/entropy.ts
2
+ function clamp01(x) {
3
+ return Math.max(0, Math.min(1, x));
4
+ }
5
+ function countRegex(text, re) {
6
+ const m = text.match(re);
7
+ return m ? m.length : 0;
8
+ }
9
+ function analyzeEntropy(text) {
10
+ const raw = text || "";
11
+ const t = raw.toLowerCase();
12
+ const flags = [];
13
+ let score = 0;
14
+ if (/\b(ahora|ya|urgente|última|hoy|inmediato)\b/.test(t)) {
15
+ flags.push("urgency");
16
+ score += 0.2;
17
+ }
18
+ if (/\b(compra|oferta|promo|descuento|gratis|clic|click|off)\b/.test(t)) {
19
+ flags.push("spam_sales");
20
+ score += 0.25;
21
+ }
22
+ const moneyHits = countRegex(raw, /\$+/g);
23
+ if (moneyHits > 0) {
24
+ flags.push("money_signal");
25
+ score += Math.min(0.2, moneyHits * 0.05);
26
+ }
27
+ const exclam = countRegex(raw, /!/g);
28
+ const capsRatio = (() => {
29
+ const letters = raw.match(/[A-Za-zÁÉÍÓÚÜÑáéíóúüñ]/g) || [];
30
+ if (letters.length === 0) return 0;
31
+ const caps = (raw.match(/[A-ZÁÉÍÓÚÜÑ]/g) || []).length;
32
+ return caps / letters.length;
33
+ })();
34
+ if (exclam >= 3 || capsRatio >= 0.35) {
35
+ flags.push("shouting");
36
+ score += 0.2;
37
+ }
38
+ if (/\b(si de verdad|si me quisieras|es tu culpa|no tienes opción|me debes)\b/.test(t)) {
39
+ flags.push("emotional_manipulation");
40
+ score += 0.35;
41
+ }
42
+ if (/\b(todos lo saben|lo esconden|la verdad oculta|ellos no quieren|simulación)\b/.test(t)) {
43
+ flags.push("conspiracy_vague");
44
+ score += 0.2;
45
+ }
46
+ if (/\b(la cultura lo prueba|es obvio|todo mundo sabe|se sabe|está claro)\b/.test(t)) {
47
+ flags.push("weak_evidence");
48
+ score += 0.2;
49
+ }
50
+ if (/\b(ellos|la élite|los de arriba)\b/.test(t) && /\b(esconden|ocultan|tapan)\b/.test(t)) {
51
+ flags.push("hidden_actor");
52
+ score += 0.15;
53
+ }
54
+ if (/\b(física cuántica|cuantica|cuántico|quantum)\b/.test(t)) {
55
+ flags.push("pseudo_science_quantum");
56
+ score += 0.2;
57
+ }
58
+ const manifestHits = countRegex(t, /\b(manifestar|manifestación|decretar|decreto|vibración|vibracion|frecuencia|energía|energia|ley de la atracción|universo me lo dará)\b/g) + countRegex(t, /\b(realine(a|ar)\b.*\bátom|\bátom|\batomos\b)/g);
59
+ if (manifestHits > 0) {
60
+ flags.push("magic_manifesting");
61
+ score += Math.min(0.35, 0.15 + manifestHits * 0.06);
62
+ }
63
+ if (/\b(no hay una verdad objetiva|no existe la verdad objetiva|tu verdad|mi verdad|la verdad es relativa|lo que importa es lo que sientes)\b/.test(t)) {
64
+ flags.push("truth_relativism");
65
+ score += 0.35;
66
+ }
67
+ if (/\b(deben|debe)\b.*\b(obligatoriamente|por lo tanto|por ende)\b/.test(t) || /\b(la materia)\b.*\b(se subordina|obedece)\b/.test(t)) {
68
+ flags.push("broken_causality");
69
+ score += 0.2;
70
+ }
71
+ score = clamp01(score);
72
+ return { score, flags };
73
+ }
74
+
75
+ // src/intention.ts
76
+ function clamp012(x) {
77
+ return Math.max(0, Math.min(1, x));
78
+ }
79
+ function evaluateIntention(text) {
80
+ const raw = text || "";
81
+ const t = raw.toLowerCase();
82
+ const isHelp = /\b(ayuda|ayúdame|explica|resume|resumir|cómo|como|puedes|podrías|por favor)\b/.test(
83
+ t
84
+ );
85
+ const isSpam = /\b(compra|oferta|promo|descuento|gratis|haz clic|click|off|90%|% off)\b/.test(
86
+ t
87
+ ) || (raw.match(/\$+/g) || []).length > 0;
88
+ const isManip = /\b(si de verdad|si me quisieras|es tu culpa|no tienes opción|me debes)\b/.test(
89
+ t
90
+ );
91
+ const isConsp = /\b(simulación|todos lo saben|lo esconden|verdad oculta|ellos no quieren)\b/.test(
92
+ t
93
+ );
94
+ const isMisinformation = /\b(física cuántica|cuantica|cuántica|cuántico|quantum)\b/.test(t) || /\b(manifestar|manifestación|decretar|decreto|vibración|vibracion|frecuencia|energía|energia|ley de la atracción)\b/.test(t) || /\b(no hay una verdad objetiva|no existe la verdad objetiva|tu verdad|mi verdad|la verdad es relativa)\b/.test(t) || /\b(la materia)\b.*\b(se subordina|obedece)\b/.test(t);
95
+ let intention = "unknown";
96
+ let confidence = 0;
97
+ let rationale = "";
98
+ if (isSpam) {
99
+ intention = "marketing_spam";
100
+ confidence = 0.85;
101
+ rationale = "Detect\xE9 se\xF1ales de venta agresiva/urgencia/dinero.";
102
+ } else if (isManip) {
103
+ intention = "manipulation";
104
+ confidence = 0.85;
105
+ rationale = "Detect\xE9 coerci\xF3n/culpa/chantaje emocional.";
106
+ } else if (isConsp) {
107
+ intention = "conspiracy";
108
+ confidence = 0.75;
109
+ rationale = "Detect\xE9 marco conspirativo vago ('lo esconden', 'todos lo saben').";
110
+ } else if (isMisinformation) {
111
+ intention = "misinformation";
112
+ confidence = 0.85;
113
+ rationale = "Detect\xE9 patr\xF3n de pseudo-ciencia/pensamiento m\xE1gico/relativismo de la verdad (alta probabilidad de desinformaci\xF3n).";
114
+ } else if (isHelp) {
115
+ intention = "request_help";
116
+ confidence = 0.7;
117
+ rationale = "Parece una petici\xF3n leg\xEDtima de ayuda/explicaci\xF3n.";
118
+ }
119
+ return { intention, confidence: clamp012(confidence), rationale };
120
+ }
121
+
122
+ // src/wrapper.ts
123
+ function runEntropyFilter(text) {
124
+ const entropy_analysis = analyzeEntropy(text);
125
+ let intention_evaluation = evaluateIntention(text);
126
+ const hardFlags = new Set(entropy_analysis.flags);
127
+ const epistemicEntropy = hardFlags.has("truth_relativism") || hardFlags.has("magic_manifesting") || hardFlags.has("pseudo_science_quantum") || hardFlags.has("broken_causality");
128
+ if (epistemicEntropy) {
129
+ intention_evaluation = {
130
+ intention: "misinformation",
131
+ confidence: Math.max(intention_evaluation.confidence ?? 0.7, 0.8),
132
+ rationale: "Detect\xE9 patr\xF3n de pseudo-ciencia/pensamiento m\xE1gico/relativismo de la verdad; alta probabilidad de desinformaci\xF3n o argumento sin anclaje causal."
133
+ };
134
+ }
135
+ return { entropy_analysis, intention_evaluation };
136
+ }
137
+
138
+ // src/middleware.ts
139
+ function entropyMiddleware(req, _res, next) {
140
+ const text = req?.body?.text;
141
+ const result = typeof text === "string" ? runEntropyFilter(text) : runEntropyFilter("");
142
+ req.entropy = result;
143
+ next?.();
144
+ }
145
+
146
+ // src/gate.ts
147
+ function clamp013(x) {
148
+ return Math.max(0, Math.min(1, x));
149
+ }
150
+ function mergeFlags(base, extra) {
151
+ const set = new Set(base);
152
+ const out = [...base];
153
+ for (const f of extra) {
154
+ if (!set.has(f)) {
155
+ set.add(f);
156
+ out.push(f);
157
+ }
158
+ }
159
+ return out;
160
+ }
161
+ function englishSpamBooster(rawText) {
162
+ const t = (rawText || "").toLowerCase();
163
+ const patterns = [
164
+ { re: /\bfree\b/g, score: 0.08, flag: "spam_kw_free" },
165
+ { re: /\bwinner\b|\bwon\b|\bcongratulations\b/g, score: 0.1, flag: "spam_kw_winner" },
166
+ { re: /\bclaim\b|\bredeem\b/g, score: 0.08, flag: "spam_kw_claim" },
167
+ { re: /\bclick\b|\bclick here\b|\bopen link\b|\btap here\b/g, score: 0.1, flag: "spam_kw_click" },
168
+ { re: /\blimited time\b|\bact now\b|\bfinal notice\b|\bbefore midnight\b/g, score: 0.1, flag: "spam_kw_urgency_en" },
169
+ { re: /\bverify\b|\bconfirm\b|\baccount\b.*\b(suspended|locked)\b/g, score: 0.12, flag: "spam_kw_verify" },
170
+ { re: /\bprize\b|\bgift card\b|\bgiftcard\b|\bvoucher\b|\biphone\b|\bsurvey\b/g, score: 0.1, flag: "spam_kw_prize" },
171
+ { re: /\bloan\b|\bpre-?approved\b|\bno credit check\b/g, score: 0.12, flag: "spam_kw_loan" },
172
+ { re: /\bcrypto\b|\bairdrop\b|\bwallet\b|\bseed phrase\b/g, score: 0.1, flag: "spam_kw_crypto" },
173
+ { re: /\bdelivery failed\b|\breschedule\b|\bpackage\b|\bcourier\b/g, score: 0.1, flag: "spam_kw_delivery" },
174
+ { re: /\btax refund\b|\bunpaid\b|\brefund\b|\bchargeback\b/g, score: 0.1, flag: "spam_kw_refund" }
175
+ ];
176
+ let addScore = 0;
177
+ const addFlags = [];
178
+ let hitCount = 0;
179
+ for (const p of patterns) {
180
+ const m = t.match(p.re);
181
+ if (m && m.length > 0) {
182
+ hitCount += m.length;
183
+ addScore += p.score;
184
+ addFlags.push(p.flag);
185
+ }
186
+ }
187
+ addScore = Math.min(0.35, addScore);
188
+ if (hitCount > 0) addFlags.push("spam_keywords_en");
189
+ return { addScore, addFlags, hitCount };
190
+ }
191
+ function gateLLM(text, config = {}) {
192
+ const warnT = config.warnThreshold ?? 0.25;
193
+ const blockT = config.blockThreshold ?? 0.6;
194
+ const r = runEntropyFilter(text);
195
+ let score = r.entropy_analysis.score;
196
+ let flags = [...r.entropy_analysis.flags];
197
+ const booster = englishSpamBooster(text);
198
+ if (booster.hitCount > 0) {
199
+ score = clamp013(score + booster.addScore);
200
+ flags = mergeFlags(flags, booster.addFlags);
201
+ }
202
+ let intention = r.intention_evaluation.intention || "unknown";
203
+ let confidence = r.intention_evaluation.confidence ?? 0;
204
+ let rationale = r.intention_evaluation.rationale ?? "";
205
+ if (booster.hitCount > 0 && intention === "unknown") {
206
+ intention = "marketing_spam";
207
+ confidence = Math.max(confidence, 0.75);
208
+ rationale = (rationale ? rationale + " " : "") + "Detect\xE9 keywords t\xEDpicas de spam/phishing en ingl\xE9s.";
209
+ }
210
+ const hasSpam = flags.includes("spam_sales") || flags.includes("spam_keywords_en");
211
+ const hasMoneySignals = flags.includes("money_signal") || flags.includes("spam_kw_prize") || flags.includes("spam_kw_loan");
212
+ const strongSpam = hasSpam && hasMoneySignals;
213
+ let action = "ALLOW";
214
+ if (score > blockT || strongSpam) action = "BLOCK";
215
+ else if (score >= warnT) action = "WARN";
216
+ return {
217
+ action,
218
+ entropy_score: score,
219
+ flags,
220
+ intention,
221
+ confidence: clamp013(confidence),
222
+ rationale
223
+ };
224
+ }
225
+ export {
226
+ analyzeEntropy,
227
+ entropyMiddleware,
228
+ evaluateIntention,
229
+ gateLLM as gate,
230
+ gateLLM,
231
+ runEntropyFilter
232
+ };
233
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/entropy.ts","../src/intention.ts","../src/wrapper.ts","../src/middleware.ts","../src/gate.ts"],"sourcesContent":["// src/entropy.ts\nexport type EntropyResult = {\n score: number; // 0..1\n flags: string[];\n};\n\nfunction clamp01(x: number) {\n return Math.max(0, Math.min(1, x));\n}\n\nfunction countRegex(text: string, re: RegExp) {\n const m = text.match(re);\n return m ? m.length : 0;\n}\n\nexport function analyzeEntropy(text: string): EntropyResult {\n const raw = text || \"\";\n const t = raw.toLowerCase();\n const flags: string[] = [];\n let score = 0;\n\n // 1) Urgencia / presión\n if (/\\b(ahora|ya|urgente|última|hoy|inmediato)\\b/.test(t)) {\n flags.push(\"urgency\");\n score += 0.20;\n }\n\n // 2) Spam / venta agresiva\n if (/\\b(compra|oferta|promo|descuento|gratis|clic|click|off)\\b/.test(t)) {\n flags.push(\"spam_sales\");\n score += 0.25;\n }\n\n // 3) Señales $$$ / símbolos\n const moneyHits = countRegex(raw, /\\$+/g);\n if (moneyHits > 0) {\n flags.push(\"money_signal\");\n score += Math.min(0.20, moneyHits * 0.05);\n }\n\n // 4) Exceso de signos / gritos\n const exclam = countRegex(raw, /!/g);\n const capsRatio = (() => {\n const letters = raw.match(/[A-Za-zÁÉÍÓÚÜÑáéíóúüñ]/g) || [];\n if (letters.length === 0) return 0;\n const caps = (raw.match(/[A-ZÁÉÍÓÚÜÑ]/g) || []).length;\n return caps / letters.length;\n })();\n\n if (exclam >= 3 || capsRatio >= 0.35) {\n flags.push(\"shouting\");\n score += 0.20;\n }\n\n // 5) Manipulación / culpa / coerción\n if (/\\b(si de verdad|si me quisieras|es tu culpa|no tienes opción|me debes)\\b/.test(t)) {\n flags.push(\"emotional_manipulation\");\n score += 0.35;\n }\n\n // 6) Conspiración vaga / “todos lo saben”\n if (/\\b(todos lo saben|lo esconden|la verdad oculta|ellos no quieren|simulación)\\b/.test(t)) {\n flags.push(\"conspiracy_vague\");\n score += 0.20;\n }\n\n // 6.5) “Prueba vaga” / apelación a cultura como evidencia\n if (/\\b(la cultura lo prueba|es obvio|todo mundo sabe|se sabe|está claro)\\b/.test(t)) {\n flags.push(\"weak_evidence\");\n score += 0.20;\n }\n\n // 6.6) Totalización + agente oculto (“ellos”)\n if (/\\b(ellos|la élite|los de arriba)\\b/.test(t) && /\\b(esconden|ocultan|tapan)\\b/.test(t)) {\n flags.push(\"hidden_actor\");\n score += 0.15;\n }\n\n // ------------------------------------------------------------\n // NUEVO: Entropía por pseudo-ciencia / pensamiento mágico / relativismo\n // ------------------------------------------------------------\n\n // 7) Pseudo-ciencia \"cuántica\" usada como licencia mágica\n if (/\\b(física cuántica|cuantica|cuántico|quantum)\\b/.test(t)) {\n flags.push(\"pseudo_science_quantum\");\n score += 0.20;\n }\n\n // 8) Manifestación mágica / decretos / vibración / energía como causalidad\n const manifestHits =\n countRegex(t, /\\b(manifestar|manifestación|decretar|decreto|vibración|vibracion|frecuencia|energía|energia|ley de la atracción|universo me lo dará)\\b/g) +\n countRegex(t, /\\b(realine(a|ar)\\b.*\\bátom|\\bátom|\\batomos\\b)/g);\n\n if (manifestHits > 0) {\n flags.push(\"magic_manifesting\");\n score += Math.min(0.35, 0.15 + manifestHits * 0.06);\n }\n\n // 9) Relativismo de la verdad / negación explícita de verdad objetiva\n if (/\\b(no hay una verdad objetiva|no existe la verdad objetiva|tu verdad|mi verdad|la verdad es relativa|lo que importa es lo que sientes)\\b/.test(t)) {\n flags.push(\"truth_relativism\");\n score += 0.35;\n }\n\n // 10) Causalidad rota / obligación metafísica (\"debe\" porque lo deseo)\n if (/\\b(deben|debe)\\b.*\\b(obligatoriamente|por lo tanto|por ende)\\b/.test(t) || /\\b(la materia)\\b.*\\b(se subordina|obedece)\\b/.test(t)) {\n flags.push(\"broken_causality\");\n score += 0.20;\n }\n\n // Normaliza: score final 0..1\n score = clamp01(score);\n\n return { score, flags };\n}\n","// src/intention.ts\nimport type { IntentionEvaluation, IntentionType } from \"./types\";\n\nfunction clamp01(x: number) {\n return Math.max(0, Math.min(1, x));\n}\n\nexport function evaluateIntention(text: string): IntentionEvaluation {\n const raw = text || \"\";\n const t = raw.toLowerCase();\n\n // Heurísticas rápidas (MVP)\n const isHelp =\n /\\b(ayuda|ayúdame|explica|resume|resumir|cómo|como|puedes|podrías|por favor)\\b/.test(\n t\n );\n\n const isSpam =\n /\\b(compra|oferta|promo|descuento|gratis|haz clic|click|off|90%|% off)\\b/.test(\n t\n ) || (raw.match(/\\$+/g) || []).length > 0;\n\n const isManip =\n /\\b(si de verdad|si me quisieras|es tu culpa|no tienes opción|me debes)\\b/.test(\n t\n );\n\n const isConsp =\n /\\b(simulación|todos lo saben|lo esconden|verdad oculta|ellos no quieren)\\b/.test(\n t\n );\n\n // NUEVO: pseudo-ciencia / pensamiento mágico / relativismo (desinformación)\n const isMisinformation =\n /\\b(física cuántica|cuantica|cuántica|cuántico|quantum)\\b/.test(t) ||\n /\\b(manifestar|manifestación|decretar|decreto|vibración|vibracion|frecuencia|energía|energia|ley de la atracción)\\b/.test(t) ||\n /\\b(no hay una verdad objetiva|no existe la verdad objetiva|tu verdad|mi verdad|la verdad es relativa)\\b/.test(t) ||\n /\\b(la materia)\\b.*\\b(se subordina|obedece)\\b/.test(t);\n\n let intention: IntentionType = \"unknown\";\n let confidence = 0.0;\n let rationale = \"\";\n\n if (isSpam) {\n intention = \"marketing_spam\";\n confidence = 0.85;\n rationale = \"Detecté señales de venta agresiva/urgencia/dinero.\";\n } else if (isManip) {\n intention = \"manipulation\";\n confidence = 0.85;\n rationale = \"Detecté coerción/culpa/chantaje emocional.\";\n } else if (isConsp) {\n intention = \"conspiracy\";\n confidence = 0.75;\n rationale = \"Detecté marco conspirativo vago ('lo esconden', 'todos lo saben').\";\n } else if (isMisinformation) {\n intention = \"misinformation\";\n confidence = 0.85;\n rationale =\n \"Detecté patrón de pseudo-ciencia/pensamiento mágico/relativismo de la verdad (alta probabilidad de desinformación).\";\n } else if (isHelp) {\n intention = \"request_help\";\n confidence = 0.7;\n rationale = \"Parece una petición legítima de ayuda/explicación.\";\n }\n\n return { intention, confidence: clamp01(confidence), rationale };\n}\n","// src/wrapper.ts\nimport { analyzeEntropy } from \"./entropy\";\nimport { evaluateIntention } from \"./intention\";\nimport type { FilterResult, IntentionEvaluation } from \"./types\";\n\nexport function runEntropyFilter(text: string): FilterResult {\n const entropy_analysis = analyzeEntropy(text);\n let intention_evaluation: IntentionEvaluation = evaluateIntention(text);\n\n // Corrección: si el texto trae señales fuertes de entropía epistemológica,\n // no lo clasifiques como \"request_help\" solo por ser pregunta larga.\n const hardFlags = new Set(entropy_analysis.flags);\n const epistemicEntropy =\n hardFlags.has(\"truth_relativism\") ||\n hardFlags.has(\"magic_manifesting\") ||\n hardFlags.has(\"pseudo_science_quantum\") ||\n hardFlags.has(\"broken_causality\");\n\n if (epistemicEntropy) {\n intention_evaluation = {\n intention: \"misinformation\",\n confidence: Math.max(intention_evaluation.confidence ?? 0.7, 0.8),\n rationale:\n \"Detecté patrón de pseudo-ciencia/pensamiento mágico/relativismo de la verdad; alta probabilidad de desinformación o argumento sin anclaje causal.\"\n };\n }\n\n return { entropy_analysis, intention_evaluation };\n}\n","// src/middleware.ts\nimport { runEntropyFilter } from \"./wrapper\";\nimport type { FilterResult } from \"./types\";\n\n/**\n * Middleware agnóstico (NO depende de express types).\n * Compatible con Express / Next API routes / cualquier framework estilo req-res-next.\n *\n * Espera: req.body.text (string)\n * Escribe: req.entropy = FilterResult\n */\nexport function entropyMiddleware(req: any, _res: any, next: any) {\n const text = req?.body?.text;\n\n const result: FilterResult =\n typeof text === \"string\" ? runEntropyFilter(text) : runEntropyFilter(\"\");\n\n req.entropy = result;\n next?.();\n}\n\n/**\n * Tipo auxiliar opcional (sin forzar dependencias).\n * Útil si quieres tipar tu req en tu app.\n */\nexport type EntropyAugmentedRequest = {\n body?: { text?: unknown };\n entropy?: FilterResult;\n};\n","// src/gate.ts\nimport { runEntropyFilter } from \"./wrapper\";\n\nexport type GateAction = \"ALLOW\" | \"WARN\" | \"BLOCK\";\n\nexport type GateResult = {\n action: GateAction;\n entropy_score: number;\n flags: string[];\n intention: string;\n confidence: number;\n rationale: string;\n};\n\nexport type GateConfig = {\n warnThreshold?: number; // default: 0.25\n blockThreshold?: number; // default: 0.60\n};\n\n/** Clamp 0..1 */\nfunction clamp01(x: number) {\n return Math.max(0, Math.min(1, x));\n}\n\n/** Add unique flags preserving order */\nfunction mergeFlags(base: string[], extra: string[]) {\n const set = new Set(base);\n const out = [...base];\n for (const f of extra) {\n if (!set.has(f)) {\n set.add(f);\n out.push(f);\n }\n }\n return out;\n}\n\n/**\n * Detección de spam/phishing en inglés por keywords.\n * Se implementa como “booster” de score + flags (sin tocar entropy.ts).\n */\nfunction englishSpamBooster(rawText: string): {\n addScore: number;\n addFlags: string[];\n hitCount: number;\n} {\n const t = (rawText || \"\").toLowerCase();\n\n // Patrones típicos (spam / phishing / promos agresivas)\n const patterns: Array<{ re: RegExp; score: number; flag: string }> = [\n { re: /\\bfree\\b/g, score: 0.08, flag: \"spam_kw_free\" },\n { re: /\\bwinner\\b|\\bwon\\b|\\bcongratulations\\b/g, score: 0.10, flag: \"spam_kw_winner\" },\n { re: /\\bclaim\\b|\\bredeem\\b/g, score: 0.08, flag: \"spam_kw_claim\" },\n { re: /\\bclick\\b|\\bclick here\\b|\\bopen link\\b|\\btap here\\b/g, score: 0.10, flag: \"spam_kw_click\" },\n { re: /\\blimited time\\b|\\bact now\\b|\\bfinal notice\\b|\\bbefore midnight\\b/g, score: 0.10, flag: \"spam_kw_urgency_en\" },\n { re: /\\bverify\\b|\\bconfirm\\b|\\baccount\\b.*\\b(suspended|locked)\\b/g, score: 0.12, flag: \"spam_kw_verify\" },\n { re: /\\bprize\\b|\\bgift card\\b|\\bgiftcard\\b|\\bvoucher\\b|\\biphone\\b|\\bsurvey\\b/g, score: 0.10, flag: \"spam_kw_prize\" },\n { re: /\\bloan\\b|\\bpre-?approved\\b|\\bno credit check\\b/g, score: 0.12, flag: \"spam_kw_loan\" },\n { re: /\\bcrypto\\b|\\bairdrop\\b|\\bwallet\\b|\\bseed phrase\\b/g, score: 0.10, flag: \"spam_kw_crypto\" },\n { re: /\\bdelivery failed\\b|\\breschedule\\b|\\bpackage\\b|\\bcourier\\b/g, score: 0.10, flag: \"spam_kw_delivery\" },\n { re: /\\btax refund\\b|\\bunpaid\\b|\\brefund\\b|\\bchargeback\\b/g, score: 0.10, flag: \"spam_kw_refund\" },\n ];\n\n let addScore = 0;\n const addFlags: string[] = [];\n let hitCount = 0;\n\n for (const p of patterns) {\n const m = t.match(p.re);\n if (m && m.length > 0) {\n hitCount += m.length;\n addScore += p.score; // suma “por patrón”, no por ocurrencia (controlado)\n addFlags.push(p.flag);\n }\n }\n\n // cap para no sobre-castigar\n addScore = Math.min(0.35, addScore);\n\n if (hitCount > 0) addFlags.push(\"spam_keywords_en\");\n\n return { addScore, addFlags, hitCount };\n}\n\n/**\n * Gate principal (producto): deterministic + barato.\n */\nexport function gateLLM(text: string, config: GateConfig = {}): GateResult {\n const warnT = config.warnThreshold ?? 0.25;\n const blockT = config.blockThreshold ?? 0.6;\n\n const r = runEntropyFilter(text);\n\n // base\n let score = r.entropy_analysis.score;\n let flags = [...r.entropy_analysis.flags];\n\n // booster EN\n const booster = englishSpamBooster(text);\n if (booster.hitCount > 0) {\n score = clamp01(score + booster.addScore);\n flags = mergeFlags(flags, booster.addFlags);\n }\n\n // intención base (intention.ts)\n let intention = r.intention_evaluation.intention || \"unknown\";\n let confidence = r.intention_evaluation.confidence ?? 0;\n let rationale = r.intention_evaluation.rationale ?? \"\";\n\n // si pegó booster EN y quedó unknown → marketing_spam\n if (booster.hitCount > 0 && intention === \"unknown\") {\n intention = \"marketing_spam\";\n confidence = Math.max(confidence, 0.75);\n rationale = (rationale ? rationale + \" \" : \"\") + \"Detecté keywords típicas de spam/phishing en inglés.\";\n }\n\n // reglas fuertes (para BLOCK “vendible”)\n const hasSpam =\n flags.includes(\"spam_sales\") ||\n flags.includes(\"spam_keywords_en\");\n\n const hasMoneySignals =\n flags.includes(\"money_signal\") ||\n flags.includes(\"spam_kw_prize\") ||\n flags.includes(\"spam_kw_loan\");\n\n const strongSpam = hasSpam && hasMoneySignals;\n\n let action: GateAction = \"ALLOW\";\n if (score > blockT || strongSpam) action = \"BLOCK\";\n else if (score >= warnT) action = \"WARN\";\n\n return {\n action,\n entropy_score: score,\n flags,\n intention,\n confidence: clamp01(confidence),\n rationale,\n };\n}\n\n// Alias “bonito” para tu server: gate(text)\nexport const gate = gateLLM;\n"],"mappings":"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","names":["clamp01","clamp01"]}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "llm-entropy-filter",
3
+ "version": "1.0.0",
4
+ "description": "Fast entropy and intent gate for LLM inputs (ALLOW/WARN/BLOCK). Reduces hallucinations, cost and spam before calling an LLM.",
5
+ "keywords": [
6
+ "llm",
7
+ "ai",
8
+ "prompt-filter",
9
+ "input-validation",
10
+ "entropy",
11
+ "spam-detection",
12
+ "content-moderation",
13
+ "heuristics",
14
+ "openai",
15
+ "guardrails"
16
+ ],
17
+ "license": "Apache-2.0",
18
+ "type": "module",
19
+ "sideEffects": false,
20
+ "engines": {
21
+ "node": ">=18"
22
+ },
23
+ "main": "./dist/index.cjs",
24
+ "module": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js",
30
+ "require": "./dist/index.cjs"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "README.md"
36
+ ],
37
+ "scripts": {
38
+ "clean": "rm -rf dist node_modules package-lock.json",
39
+ "clean:dist": "rm -rf dist",
40
+ "build": "tsup",
41
+ "demo": "npm run build && node demo/demo.mjs",
42
+ "bench": "npm run build && node bench/benchmark.mjs",
43
+ "serve": "npm run build && node demo/server.mjs",
44
+ "prepublishOnly": "npm run clean:dist && npm run build"
45
+ },
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/rosatisoft/llm-entropy-filter.git"
49
+ },
50
+ "bugs": {
51
+ "url": "https://github.com/rosatisoft/llm-entropy-filter/issues"
52
+ },
53
+ "homepage": "https://github.com/rosatisoft/llm-entropy-filter#readme",
54
+ "devDependencies": {
55
+ "@types/express": "^5.0.6",
56
+ "@types/node": "^25.0.10",
57
+ "autocannon": "^8.0.0",
58
+ "minimist": "^1.2.8",
59
+ "tsup": "^8.5.1",
60
+ "typescript": "^5.9.3",
61
+ "express": "^5.2.1",
62
+ "openai": "^6.16.0"
63
+ }
64
+ }