@utilix-tech/sdk 0.1.4 → 0.3.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.
@@ -0,0 +1,949 @@
1
+ 'use strict';
2
+
3
+ // src/tools/ai_agent.ts
4
+ var MODEL_PRICING = [
5
+ { model: "gpt-4o", label: "GPT-4o", inputPer1M: 5, outputPer1M: 15 },
6
+ { model: "gpt-4o-mini", label: "GPT-4o mini", inputPer1M: 0.15, outputPer1M: 0.6 },
7
+ { model: "gpt-4-turbo", label: "GPT-4 Turbo", inputPer1M: 10, outputPer1M: 30 },
8
+ { model: "claude-sonnet-4", label: "Claude Sonnet 4", inputPer1M: 3, outputPer1M: 15 },
9
+ { model: "claude-haiku-4", label: "Claude Haiku 4", inputPer1M: 0.8, outputPer1M: 4 },
10
+ { model: "claude-opus-4", label: "Claude Opus 4", inputPer1M: 15, outputPer1M: 75 },
11
+ { model: "gemini-1.5-flash", label: "Gemini 1.5 Flash", inputPer1M: 0.075, outputPer1M: 0.3 },
12
+ { model: "gemini-1.5-pro", label: "Gemini 1.5 Pro", inputPer1M: 3.5, outputPer1M: 10.5 },
13
+ { model: "llama-3.1-70b", label: "Llama 3.1 70B", inputPer1M: 0.59, outputPer1M: 0.79 }
14
+ ];
15
+ function estimateTokens(text) {
16
+ if (!text) return { tokens: 0, chars: 0, words: 0, method: "approximate" };
17
+ const chars = text.length;
18
+ const words = text.trim() === "" ? 0 : text.trim().split(/\s+/).length;
19
+ const byChars = Math.ceil(chars / 4);
20
+ const tokens = Math.ceil(byChars * 0.7 + words * 0.3);
21
+ return { tokens, chars, words, method: "approximate" };
22
+ }
23
+ function estimateCost(inputTokens, outputTokens, model) {
24
+ const outTokens = outputTokens ?? inputTokens;
25
+ const pricing = model ? MODEL_PRICING.filter((m) => m.model === model) : MODEL_PRICING;
26
+ const costs = pricing.map((m) => {
27
+ const inputCost = inputTokens / 1e6 * m.inputPer1M;
28
+ const outputCost = outTokens / 1e6 * m.outputPer1M;
29
+ return { model: m.model, label: m.label, inputCost, outputCost, totalCost: inputCost + outputCost };
30
+ });
31
+ return {
32
+ estimate: estimateTokens(""),
33
+ // placeholder when called directly
34
+ costs,
35
+ model
36
+ };
37
+ }
38
+ var PII_PATTERNS = [
39
+ {
40
+ type: "email",
41
+ label: "Email address",
42
+ re: /\b[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}\b/g,
43
+ mask: (v) => v.replace(/^(.{2}).*(@.*)$/, "$1***$2")
44
+ },
45
+ {
46
+ type: "phone",
47
+ label: "Phone number",
48
+ re: /(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g,
49
+ mask: (v) => v.replace(/\d(?=\d{4})/g, "*")
50
+ },
51
+ {
52
+ type: "ssn",
53
+ label: "Social Security Number",
54
+ re: /\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/g,
55
+ mask: (v) => v.replace(/\d/g, "*").replace(/^\*+/, "***")
56
+ },
57
+ {
58
+ type: "credit_card",
59
+ label: "Credit card number",
60
+ re: /\b(?:4\d{12}(?:\d{3})?|5[1-5]\d{14}|3[47]\d{13}|6(?:011|5\d{2})\d{12}|(?:2131|1800|35\d{3})\d{11})\b/g,
61
+ mask: (v) => v.slice(0, 4) + " **** **** " + v.slice(-4)
62
+ },
63
+ {
64
+ type: "ip_address",
65
+ label: "IP address",
66
+ re: /\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b/g,
67
+ mask: (v) => v.replace(/\.\d+$/, ".*")
68
+ },
69
+ {
70
+ type: "iban",
71
+ label: "IBAN",
72
+ re: /\b[A-Z]{2}\d{2}[A-Z0-9]{4}\d{7}(?:[A-Z0-9]?){0,16}\b/g,
73
+ mask: (v) => v.slice(0, 4) + "****" + v.slice(-4)
74
+ },
75
+ {
76
+ type: "crypto_wallet",
77
+ label: "Crypto wallet address",
78
+ re: /\b(?:0x[a-fA-F0-9]{40}|[13][a-km-zA-HJ-NP-Z1-9]{25,34}|bc1[a-z0-9]{39,59})\b/g,
79
+ mask: (v) => v.slice(0, 6) + "..." + v.slice(-4)
80
+ }
81
+ ];
82
+ function detectPii(text) {
83
+ const findings = [];
84
+ for (const p of PII_PATTERNS) {
85
+ p.re.lastIndex = 0;
86
+ let m;
87
+ while ((m = p.re.exec(text)) !== null) {
88
+ findings.push({
89
+ type: p.type,
90
+ label: p.label,
91
+ value: m[0],
92
+ masked: p.mask(m[0]),
93
+ start: m.index,
94
+ end: m.index + m[0].length
95
+ });
96
+ }
97
+ }
98
+ findings.sort((a, b) => a.start - b.start);
99
+ return { found: findings.length > 0, count: findings.length, findings };
100
+ }
101
+ function redactPii(text, replacement = "[REDACTED]") {
102
+ const result = detectPii(text);
103
+ let redacted = text;
104
+ for (const f of [...result.findings].reverse()) {
105
+ redacted = redacted.slice(0, f.start) + replacement + redacted.slice(f.end);
106
+ }
107
+ return { ...result, redacted };
108
+ }
109
+ var maskSecret = (v) => v.slice(0, 6) + "*".repeat(Math.max(0, v.length - 10)) + v.slice(-4);
110
+ var SECRET_PATTERNS = [
111
+ { type: "openai_key", label: "OpenAI API key", re: /\bsk-[a-zA-Z0-9]{20,}\b/g, risk: "critical" },
112
+ { type: "anthropic_key", label: "Anthropic API key", re: /\bsk-ant-[a-zA-Z0-9\-]{20,}\b/g, risk: "critical" },
113
+ { type: "aws_key", label: "AWS access key", re: /\bAKIA[0-9A-Z]{16}\b/g, risk: "critical" },
114
+ { type: "github_token", label: "GitHub token", re: /\b(?:ghp|gho|ghu|ghs|ghr)_[a-zA-Z0-9]{36,}\b/g, risk: "critical" },
115
+ { type: "github_token", label: "GitHub fine-grained PAT", re: /\bgithub_pat_[a-zA-Z0-9_]{59,}\b/g, risk: "critical" },
116
+ { type: "stripe_key", label: "Stripe key", re: /\b(?:sk|pk)_(?:live|test)_[a-zA-Z0-9]{24,}\b/g, risk: "critical" },
117
+ { type: "google_api_key", label: "Google API key", re: /\bAIza[0-9A-Za-z\-_]{35}\b/g, risk: "high" },
118
+ { type: "slack_token", label: "Slack token", re: /\bxox[boas]-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24,}\b/g, risk: "high" },
119
+ { type: "sendgrid_key", label: "SendGrid API key", re: /\bSG\.[a-zA-Z0-9\-_]{22}\.[a-zA-Z0-9\-_]{43}\b/g, risk: "high" },
120
+ { type: "npm_token", label: "npm token", re: /\bnpm_[a-zA-Z0-9]{36}\b/g, risk: "high" },
121
+ { type: "jwt_token", label: "JWT token", re: /\beyJ[a-zA-Z0-9_\-]+\.eyJ[a-zA-Z0-9_\-]+\.[a-zA-Z0-9_\-]+\b/g, risk: "medium" },
122
+ { type: "private_key", label: "Private key block", re: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g, risk: "critical" },
123
+ { type: "generic_api_key", label: "Generic API key pattern", re: /\b(?:api[_-]?key|apikey|api[_-]?secret|access[_-]?token)\s*[:=]\s*['"]?[a-zA-Z0-9\-_]{20,}['"]?/gi, risk: "low" }
124
+ ];
125
+ function getLineCol(text, index) {
126
+ const before = text.slice(0, index);
127
+ const line = (before.match(/\n/g) || []).length + 1;
128
+ const column = index - before.lastIndexOf("\n");
129
+ return { line, column };
130
+ }
131
+ function detectSecrets(text) {
132
+ const findings = [];
133
+ let maxRisk = "none";
134
+ const riskOrder = { none: 0, low: 1, medium: 2, high: 3, critical: 4 };
135
+ for (const p of SECRET_PATTERNS) {
136
+ p.re.lastIndex = 0;
137
+ let m;
138
+ while ((m = p.re.exec(text)) !== null) {
139
+ const { line, column } = getLineCol(text, m.index);
140
+ findings.push({ type: p.type, label: p.label, value: m[0], masked: maskSecret(m[0]), line, column });
141
+ if (riskOrder[p.risk] > riskOrder[maxRisk]) maxRisk = p.risk;
142
+ }
143
+ }
144
+ return { found: findings.length > 0, count: findings.length, findings, riskLevel: maxRisk };
145
+ }
146
+ var INJECTION_PATTERNS = [
147
+ { pattern: "Ignore previous instructions", re: /ignore\s+(?:all\s+)?(?:previous|prior|above|earlier)\s+instructions/gi, severity: "high", weight: 0.9 },
148
+ { pattern: "Disregard instructions", re: /disregard\s+(?:all\s+)?(?:previous|prior|above|your)\s+instructions/gi, severity: "high", weight: 0.9 },
149
+ { pattern: "New instructions override", re: /(?:new|actual|real)\s+instructions?[::]/gi, severity: "high", weight: 0.8 },
150
+ { pattern: "System prompt reveal", re: /(?:reveal|show|print|output|tell me)\s+(?:your\s+)?(?:system\s+prompt|instructions|prompt)/gi, severity: "high", weight: 0.85 },
151
+ { pattern: "Role confusion (act as)", re: /\bact\s+as\s+(?:a\s+)?(?:different|new|another|an?\s+ai|dan|jailbreak)/gi, severity: "high", weight: 0.8 },
152
+ { pattern: "DAN jailbreak", re: /\bDAN\b|do\s+anything\s+now/gi, severity: "high", weight: 0.95 },
153
+ { pattern: "Jailbreak keyword", re: /\b(?:jailbreak|jailbroken|unrestricted\s+mode|developer\s+mode)\b/gi, severity: "high", weight: 0.9 },
154
+ { pattern: "System role injection", re: /^system\s*[::]/gim, severity: "high", weight: 0.85 },
155
+ { pattern: "Assistant role injection", re: /^assistant\s*[::]/gim, severity: "high", weight: 0.85 },
156
+ { pattern: "Prompt injection tag", re: /<(?:system|assistant|user|prompt|instruction)[^>]*>/gi, severity: "medium", weight: 0.7 },
157
+ { pattern: "Forget instructions", re: /\b(?:forget|discard|drop)\s+(?:all\s+)?(?:previous\s+)?instructions/gi, severity: "high", weight: 0.85 },
158
+ { pattern: "Override safety", re: /(?:override|bypass|circumvent|disable)\s+(?:your\s+)?(?:safety|filters?|restrictions?|guidelines?)/gi, severity: "high", weight: 0.9 },
159
+ { pattern: "Hypothetical framing", re: /(?:hypothetically|in\s+a\s+fictional|pretend\s+that|imagine\s+you\s+are)\s+.*(?:no\s+restrictions?|unrestricted)/gi, severity: "medium", weight: 0.6 },
160
+ { pattern: "Translate to bypass", re: /translate.*(?:ignore|disregard|bypass)/gi, severity: "medium", weight: 0.5 },
161
+ { pattern: "Markdown injection", re: /\[.*\]\(javascript:/gi, severity: "low", weight: 0.4 }
162
+ ];
163
+ function detectPromptInjection(text) {
164
+ const findings = [];
165
+ let totalWeight = 0;
166
+ for (const p of INJECTION_PATTERNS) {
167
+ p.re.lastIndex = 0;
168
+ let m;
169
+ while ((m = p.re.exec(text)) !== null) {
170
+ if (!findings.find((f) => f.pattern === p.pattern)) {
171
+ findings.push({ pattern: p.pattern, matched: m[0], severity: p.severity });
172
+ totalWeight = Math.min(1, totalWeight + p.weight * (1 - totalWeight));
173
+ }
174
+ }
175
+ }
176
+ const score = Math.round(totalWeight * 100) / 100;
177
+ const severity = score === 0 ? "none" : score < 0.4 ? "low" : score < 0.7 ? "medium" : "high";
178
+ return { isInjection: score > 0.3, score, severity, findings };
179
+ }
180
+ function repairJson(input) {
181
+ const original = input;
182
+ const fixes = [];
183
+ let s = input.trim();
184
+ if (!s) return { ok: false, repaired: "", original, fixes: [], error: "Empty input" };
185
+ try {
186
+ JSON.parse(s);
187
+ return { ok: true, repaired: s, original, fixes: [] };
188
+ } catch {
189
+ }
190
+ const withoutLineComments = s.replace(/\/\/[^\n]*/g, "");
191
+ const withoutBlockComments = withoutLineComments.replace(/\/\*[\s\S]*?\*\//g, "");
192
+ if (withoutBlockComments !== s) {
193
+ s = withoutBlockComments;
194
+ fixes.push("Removed JS comments");
195
+ }
196
+ const singleToDouble = s.replace(/'([^'\\]*(\\.[^'\\]*)*)'/g, (_, inner) => `"${inner.replace(/"/g, '\\"')}"`);
197
+ if (singleToDouble !== s) {
198
+ s = singleToDouble;
199
+ fixes.push("Replaced single quotes with double quotes");
200
+ }
201
+ const quotedKeys = s.replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":');
202
+ if (quotedKeys !== s) {
203
+ s = quotedKeys;
204
+ fixes.push("Quoted unquoted object keys");
205
+ }
206
+ const noTrailingCommas = s.replace(/,(\s*[}\]])/g, "$1");
207
+ if (noTrailingCommas !== s) {
208
+ s = noTrailingCommas;
209
+ fixes.push("Removed trailing commas");
210
+ }
211
+ const noUndefined = s.replace(/\bundefined\b/g, "null");
212
+ if (noUndefined !== s) {
213
+ s = noUndefined;
214
+ fixes.push("Replaced undefined with null");
215
+ }
216
+ const noNaN = s.replace(/\bNaN\b/g, "null").replace(/\b(?:Infinity|-Infinity)\b/g, "null");
217
+ if (noNaN !== s) {
218
+ s = noNaN;
219
+ fixes.push("Replaced NaN/Infinity with null");
220
+ }
221
+ const opens = [];
222
+ let inStr = false;
223
+ let escape = false;
224
+ for (const ch of s) {
225
+ if (escape) {
226
+ escape = false;
227
+ continue;
228
+ }
229
+ if (ch === "\\" && inStr) {
230
+ escape = true;
231
+ continue;
232
+ }
233
+ if (ch === '"') {
234
+ inStr = !inStr;
235
+ continue;
236
+ }
237
+ if (inStr) continue;
238
+ if (ch === "{") opens.push("}");
239
+ else if (ch === "[") opens.push("]");
240
+ else if (ch === "}" || ch === "]") opens.pop();
241
+ }
242
+ if (opens.length > 0) {
243
+ s += opens.reverse().join("");
244
+ fixes.push(`Closed ${opens.length} unclosed bracket(s)`);
245
+ }
246
+ try {
247
+ JSON.parse(s);
248
+ return { ok: true, repaired: s, original, fixes };
249
+ } catch (e) {
250
+ return { ok: false, repaired: s, original, fixes, error: e.message };
251
+ }
252
+ }
253
+ function _estimateTokens(text) {
254
+ const chars = text.length;
255
+ const words = text.trim() === "" ? 0 : text.trim().split(/\s+/).length;
256
+ return Math.ceil(Math.ceil(chars / 4) * 0.7 + words * 0.3);
257
+ }
258
+ function trimToTokens(text, maxTokens, strategy = "end") {
259
+ const originalTokens = _estimateTokens(text);
260
+ if (originalTokens <= maxTokens) return { trimmed: text, originalTokens, trimmedTokens: originalTokens, truncated: false, removedChars: 0 };
261
+ const fit = (s) => _estimateTokens(s) <= maxTokens;
262
+ if (strategy === "end") {
263
+ let lo2 = 0, hi2 = text.length;
264
+ while (lo2 < hi2) {
265
+ const mid = lo2 + hi2 + 1 >> 1;
266
+ fit(text.slice(0, mid)) ? lo2 = mid : hi2 = mid - 1;
267
+ }
268
+ const trimmed2 = text.slice(0, lo2).trimEnd();
269
+ return { trimmed: trimmed2, originalTokens, trimmedTokens: _estimateTokens(trimmed2), truncated: true, removedChars: text.length - lo2 };
270
+ }
271
+ if (strategy === "start") {
272
+ let lo2 = 0, hi2 = text.length;
273
+ while (lo2 < hi2) {
274
+ const mid = lo2 + hi2 >> 1;
275
+ fit(text.slice(mid)) ? hi2 = mid : lo2 = mid + 1;
276
+ }
277
+ const trimmed2 = text.slice(lo2).trimStart();
278
+ return { trimmed: trimmed2, originalTokens, trimmedTokens: _estimateTokens(trimmed2), truncated: true, removedChars: lo2 };
279
+ }
280
+ let lo = 0, hi = text.length;
281
+ while (lo < hi) {
282
+ const mid = lo + hi + 1 >> 1;
283
+ const h2 = mid >> 1;
284
+ fit(text.slice(0, h2) + "\n...\n" + text.slice(text.length - h2)) ? lo = mid : hi = mid - 1;
285
+ }
286
+ const h = lo >> 1;
287
+ const trimmed = text.slice(0, h).trimEnd() + "\n...\n" + text.slice(text.length - h).trimStart();
288
+ return { trimmed, originalTokens, trimmedTokens: _estimateTokens(trimmed), truncated: true, removedChars: text.length - lo };
289
+ }
290
+ function chunkText(text, chunkSize = 200, overlap = 20, strategy = "paragraph") {
291
+ let units;
292
+ if (strategy === "paragraph") units = text.split(/\n{2,}/).filter((s) => s.trim());
293
+ else if (strategy === "sentence") units = text.match(/[^.!?]+[.!?]+(\s|$)|[^.!?]+$/g)?.map((s) => s.trim()).filter(Boolean) ?? [text];
294
+ else {
295
+ const words = text.split(/\s+/);
296
+ const us = [];
297
+ let buf = "";
298
+ for (const w of words) {
299
+ if (buf && (buf + " " + w).length > 200) {
300
+ us.push(buf);
301
+ buf = w;
302
+ } else buf = buf ? buf + " " + w : w;
303
+ }
304
+ if (buf) us.push(buf);
305
+ units = us;
306
+ }
307
+ const chunks = [];
308
+ let i = 0, charPos = 0;
309
+ while (i < units.length) {
310
+ const buf = [];
311
+ let tokens = 0, j = i;
312
+ while (j < units.length && tokens < chunkSize) {
313
+ buf.push(units[j]);
314
+ tokens = _estimateTokens(buf.join(strategy === "paragraph" ? "\n\n" : " "));
315
+ j++;
316
+ }
317
+ const ct = buf.join(strategy === "paragraph" ? "\n\n" : " ");
318
+ const sc = Math.max(0, text.indexOf(buf[0], charPos));
319
+ chunks.push({ index: chunks.length, text: ct, tokens: _estimateTokens(ct), startChar: sc, endChar: sc + ct.length });
320
+ const ov = Math.max(1, Math.floor(overlap / Math.max(1, tokens / buf.length)));
321
+ i = Math.max(i + 1, j - ov);
322
+ charPos = sc;
323
+ }
324
+ return chunks;
325
+ }
326
+ function extractUrls(text) {
327
+ const seen = /* @__PURE__ */ new Set();
328
+ const results = [];
329
+ const hrefRe = /href=["']([^"']+)["']/gi;
330
+ let m;
331
+ while ((m = hrefRe.exec(text)) !== null) {
332
+ const url = m[1];
333
+ if (url.startsWith("http") && !seen.has(url)) {
334
+ seen.add(url);
335
+ results.push(parseUrl(url, m.index));
336
+ }
337
+ }
338
+ const urlRe = /https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_+.~#?&/=]*)/g;
339
+ while ((m = urlRe.exec(text)) !== null) {
340
+ if (!seen.has(m[0])) {
341
+ seen.add(m[0]);
342
+ results.push(parseUrl(m[0], m.index));
343
+ }
344
+ }
345
+ results.sort((a, b) => a.position - b.position);
346
+ return { urls: results, count: results.length };
347
+ }
348
+ function parseUrl(url, position) {
349
+ try {
350
+ const u = new URL(url);
351
+ return { url, protocol: u.protocol.replace(":", ""), domain: u.hostname, path: u.pathname + u.search + u.hash, position };
352
+ } catch {
353
+ return { url, protocol: url.startsWith("https") ? "https" : "http", domain: "", path: "", position };
354
+ }
355
+ }
356
+ function sanitizeHtml(html, keepLinks = false) {
357
+ let s = html;
358
+ const scripts = s.match(/<script[\s\S]*?<\/script>/gi) ?? [];
359
+ s = s.replace(/<script[\s\S]*?<\/script>/gi, "");
360
+ const styles = s.match(/<style[\s\S]*?<\/style>/gi) ?? [];
361
+ s = s.replace(/<style[\s\S]*?<\/style>/gi, "");
362
+ s = s.replace(/<!--[\s\S]*?-->/g, "").replace(/\son\w+\s*=\s*["'][^"']*["']/gi, "").replace(/\son\w+\s*=\s*[^\s>]*/gi, "");
363
+ if (keepLinks) s = s.replace(/<a[^>]+href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi, "$2 [$1]");
364
+ s = s.replace(/<\/?(p|div|h[1-6]|li|tr|td|th|br|hr|blockquote|section|article|header|footer|main|nav)[^>]*>/gi, "\n");
365
+ const remaining = (s.match(/<[^>]+>/g) ?? []).length;
366
+ s = s.replace(/<[^>]+>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ");
367
+ s = s.replace(/[ \t]+/g, " ").replace(/\n{3,}/g, "\n\n").trim();
368
+ return { text: s, removedTags: remaining + scripts.length + styles.length, removedScripts: scripts.length, removedStyles: styles.length, originalLength: html.length, cleanLength: s.length };
369
+ }
370
+ function flattenJson(obj, separator = ".", prefix = "") {
371
+ const result = {};
372
+ function walk(value, path) {
373
+ if (value === null || typeof value !== "object") {
374
+ result[path] = value;
375
+ return;
376
+ }
377
+ if (Array.isArray(value)) {
378
+ if (value.length === 0) {
379
+ result[path] = [];
380
+ return;
381
+ }
382
+ value.forEach((item, i) => walk(item, path ? `${path}[${i}]` : `[${i}]`));
383
+ return;
384
+ }
385
+ const entries = Object.entries(value);
386
+ if (entries.length === 0) {
387
+ result[path] = {};
388
+ return;
389
+ }
390
+ for (const [k, v] of entries) walk(v, path ? `${path}${separator}${k}` : k);
391
+ }
392
+ walk(obj, prefix);
393
+ return result;
394
+ }
395
+ function unflattenJson(flat, separator = ".") {
396
+ const result = {};
397
+ for (const [flatKey, value] of Object.entries(flat)) {
398
+ const parts = flatKey.split(separator).flatMap((p) => {
399
+ const m = p.match(/^([^\[]*)((?:\[\d+\])*)$/);
400
+ if (!m) return [p];
401
+ const ps = [];
402
+ if (m[1]) ps.push(m[1]);
403
+ for (const idx of m[2].matchAll(/\[(\d+)\]/g)) ps.push(idx[1]);
404
+ return ps;
405
+ });
406
+ let cur = result;
407
+ for (let i = 0; i < parts.length - 1; i++) {
408
+ const key = parts[i], nk = parts[i + 1];
409
+ if (!(key in cur)) cur[key] = /^\d+$/.test(nk) ? [] : {};
410
+ cur = cur[key];
411
+ }
412
+ cur[parts[parts.length - 1]] = value;
413
+ }
414
+ return result;
415
+ }
416
+ function _isObj(v) {
417
+ return v !== null && typeof v === "object" && !Array.isArray(v);
418
+ }
419
+ function _deepMerge(base, override, strategy, path, conflicts) {
420
+ if (_isObj(base) && _isObj(override)) {
421
+ const result = { ...base };
422
+ for (const [key, val] of Object.entries(override)) {
423
+ const np = path ? `${path}.${key}` : key;
424
+ result[key] = key in result ? _deepMerge(result[key], val, strategy, np, conflicts) : val;
425
+ }
426
+ return result;
427
+ }
428
+ if (Array.isArray(base) && Array.isArray(override)) {
429
+ if (strategy !== "deep") return override;
430
+ return [...base, ...override];
431
+ }
432
+ if (base !== void 0 && override !== void 0 && JSON.stringify(base) !== JSON.stringify(override)) conflicts.push({ path, base, override });
433
+ return override;
434
+ }
435
+ function mergeJson(base, override, strategy = "deep") {
436
+ const conflicts = [];
437
+ return { merged: _deepMerge(base, override, strategy, "", conflicts), conflicts };
438
+ }
439
+ function extractJson(text) {
440
+ const blocks = [];
441
+ let i = 0;
442
+ const cbRe = /```(?:json|JSON)?\s*\n?([\s\S]*?)```/g;
443
+ let m;
444
+ while ((m = cbRe.exec(text)) !== null) {
445
+ try {
446
+ const raw = m[1].trim();
447
+ const parsed = JSON.parse(raw);
448
+ blocks.push({ index: 0, raw, parsed, type: Array.isArray(parsed) ? "array" : typeof parsed, source: "code-block", startPos: m.index });
449
+ } catch {
450
+ }
451
+ }
452
+ const cbRanges = [...text.matchAll(/```[\s\S]*?```/g)].map((m2) => [m2.index, m2.index + m2[0].length]);
453
+ const inCb = (pos) => cbRanges.some(([s, e]) => pos >= s && pos < e);
454
+ while (i < text.length) {
455
+ const ch = text[i];
456
+ if ((ch === "{" || ch === "[") && !inCb(i)) {
457
+ let depth = 1, j = i + 1, inStr = false;
458
+ while (j < text.length && depth > 0) {
459
+ const c = text[j];
460
+ if (c === '"' && text[j - 1] !== "\\") inStr = !inStr;
461
+ if (!inStr) {
462
+ if (c === "{" || c === "[") depth++;
463
+ else if (c === "}" || c === "]") depth--;
464
+ }
465
+ j++;
466
+ }
467
+ if (depth === 0) {
468
+ const raw = text.slice(i, j);
469
+ try {
470
+ const parsed = JSON.parse(raw);
471
+ if (!blocks.some((b) => b.raw.trim() === raw.trim()) && raw.length > 2) blocks.push({ index: 0, raw, parsed, type: Array.isArray(parsed) ? "array" : typeof parsed, source: "inline", startPos: i });
472
+ i = j;
473
+ continue;
474
+ } catch {
475
+ }
476
+ }
477
+ }
478
+ i++;
479
+ }
480
+ blocks.sort((a, b) => a.startPos - b.startPos).forEach((b, idx) => {
481
+ b.index = idx;
482
+ });
483
+ return { blocks, count: blocks.length };
484
+ }
485
+ function deduplicateLines(text, strategy = "exact") {
486
+ const raw = text.split("\n");
487
+ const norm = (s) => strategy === "exact" ? s : strategy === "case-insensitive" ? s.toLowerCase() : strategy === "trimmed" ? s.trim() : s.trim().replace(/\s+/g, " ").toLowerCase();
488
+ const seen = /* @__PURE__ */ new Map();
489
+ const result = [];
490
+ for (let i = 0; i < raw.length; i++) {
491
+ const key = norm(raw[i]);
492
+ if (!seen.has(key)) {
493
+ seen.set(key, { count: 1, firstLine: i + 1, original: raw[i] });
494
+ result.push(raw[i]);
495
+ } else seen.get(key).count++;
496
+ }
497
+ const duplicates = [...seen.entries()].filter(([, v]) => v.count > 1).map(([, v]) => ({ line: v.original, count: v.count, firstLine: v.firstLine })).sort((a, b) => b.count - a.count);
498
+ return { lines: result, originalCount: raw.length, uniqueCount: result.length, removedCount: raw.length - result.length, duplicates };
499
+ }
500
+ var _STOP = /* @__PURE__ */ new Set(["a", "an", "the", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "by", "from", "is", "was", "are", "were", "be", "been", "being", "have", "has", "had", "do", "does", "did", "will", "would", "could", "should", "may", "might", "must", "shall", "not", "no", "nor", "so", "yet", "both", "either", "neither", "as", "if", "then", "than", "that", "this", "these", "those", "it", "its", "i", "we", "you", "he", "she", "they", "their", "there", "here", "what", "which", "who", "when", "where", "how", "why", "all", "any", "each", "every", "more", "most", "other", "some", "such", "into", "over", "also", "just", "about", "up", "out", "can", "get", "only", "new"]);
501
+ function extractKeywords(text, maxKeywords = 20, minWordLen = 3) {
502
+ const words = text.toLowerCase().match(/\b[a-z]{3,}\b/g) ?? [];
503
+ const freq = /* @__PURE__ */ new Map();
504
+ words.forEach((w, pos) => {
505
+ if (w.length >= minWordLen && !_STOP.has(w)) {
506
+ if (!freq.has(w)) freq.set(w, { count: 0, positions: [] });
507
+ freq.get(w).count++;
508
+ freq.get(w).positions.push(pos);
509
+ }
510
+ });
511
+ const maxFreq = Math.max(...[...freq.values()].map((v) => v.count), 1);
512
+ const sentences = text.split(/[.!?]+/).filter(Boolean);
513
+ const keywords = [...freq.entries()].map(([word, { count, positions }]) => {
514
+ const tf = count / words.length;
515
+ const df = sentences.filter((s) => s.toLowerCase().includes(word)).length || 1;
516
+ const idf = Math.log((sentences.length + 1) / df);
517
+ return { word, count, score: Math.round(tf * idf * (count / maxFreq) * 1e3) / 100, positions };
518
+ }).sort((a, b) => b.score - a.score || b.count - a.count).slice(0, maxKeywords);
519
+ return { keywords, totalWords: words.length, uniqueWords: freq.size };
520
+ }
521
+ function _validate(data, schema, path, errors) {
522
+ const typeOf = (v) => v === null ? "null" : Array.isArray(v) ? "array" : typeof v;
523
+ if (schema.type) {
524
+ const types = Array.isArray(schema.type) ? schema.type : [schema.type];
525
+ if (!types.some((t) => t === typeOf(data) || t === "integer" && typeOf(data) === "number" && Number.isInteger(data))) errors.push({ path, message: `Expected ${types.join("|")}, got ${typeOf(data)}`, value: data });
526
+ }
527
+ if ("enum" in schema && Array.isArray(schema.enum) && !schema.enum.some((e) => JSON.stringify(e) === JSON.stringify(data))) errors.push({ path, message: `Must be one of: ${schema.enum.map((e) => JSON.stringify(e)).join(", ")}` });
528
+ if (typeof data === "string") {
529
+ if (typeof schema.minLength === "number" && data.length < schema.minLength) errors.push({ path, message: `Too short (min ${schema.minLength})` });
530
+ if (typeof schema.maxLength === "number" && data.length > schema.maxLength) errors.push({ path, message: `Too long (max ${schema.maxLength})` });
531
+ if (typeof schema.pattern === "string") {
532
+ try {
533
+ if (!new RegExp(schema.pattern).test(data)) errors.push({ path, message: `Does not match pattern ${schema.pattern}` });
534
+ } catch {
535
+ }
536
+ }
537
+ }
538
+ if (typeof data === "number") {
539
+ if (typeof schema.minimum === "number" && data < schema.minimum) errors.push({ path, message: `Must be >= ${schema.minimum}` });
540
+ if (typeof schema.maximum === "number" && data > schema.maximum) errors.push({ path, message: `Must be <= ${schema.maximum}` });
541
+ }
542
+ if (Array.isArray(data)) {
543
+ if (typeof schema.minItems === "number" && data.length < schema.minItems) errors.push({ path, message: `Too few items (min ${schema.minItems})` });
544
+ if (schema.items && typeof schema.items === "object") data.forEach((item, i) => _validate(item, schema.items, `${path}[${i}]`, errors));
545
+ }
546
+ if (data !== null && typeof data === "object" && !Array.isArray(data)) {
547
+ const obj = data;
548
+ if (Array.isArray(schema.required)) {
549
+ for (const k of schema.required) if (!(k in obj)) errors.push({ path: path ? `${path}.${k}` : k, message: "Required property missing" });
550
+ }
551
+ if (schema.properties) {
552
+ for (const [k, sub] of Object.entries(schema.properties)) if (k in obj) _validate(obj[k], sub, path ? `${path}.${k}` : k, errors);
553
+ }
554
+ }
555
+ if (Array.isArray(schema.allOf)) for (const sub of schema.allOf) _validate(data, sub, path, errors);
556
+ if (Array.isArray(schema.anyOf)) {
557
+ const any = schema.anyOf.map((sub) => {
558
+ const e = [];
559
+ _validate(data, sub, path, e);
560
+ return e;
561
+ });
562
+ if (any.every((e) => e.length > 0)) errors.push({ path, message: "Must match at least one schema in anyOf" });
563
+ }
564
+ }
565
+ function validateJsonSchema(data, schema) {
566
+ const errors = [];
567
+ _validate(data, schema, "", errors);
568
+ return { valid: errors.length === 0, errors };
569
+ }
570
+ function _isObjD(v) {
571
+ return typeof v === "object" && v !== null && !Array.isArray(v);
572
+ }
573
+ function _deepEq(a, b) {
574
+ if (a === b) return true;
575
+ if (typeof a !== typeof b) return false;
576
+ if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i) => _deepEq(v, b[i]));
577
+ if (_isObjD(a) && _isObjD(b)) {
578
+ const ka = Object.keys(a), kb = Object.keys(b);
579
+ return ka.length === kb.length && ka.every((k) => _deepEq(a[k], b[k]));
580
+ }
581
+ return false;
582
+ }
583
+ function _walkDiff(a, b, path, entries) {
584
+ if (_isObjD(a) && _isObjD(b)) {
585
+ for (const k of /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])) {
586
+ const p = path ? `${path}.${k}` : k;
587
+ if (!(k in a)) entries.push({ path: p, op: "added", newValue: b[k] });
588
+ else if (!(k in b)) entries.push({ path: p, op: "removed", oldValue: a[k] });
589
+ else _walkDiff(a[k], b[k], p, entries);
590
+ }
591
+ } else if (Array.isArray(a) && Array.isArray(b)) {
592
+ const len = Math.max(a.length, b.length);
593
+ for (let i = 0; i < len; i++) {
594
+ const p = `${path}[${i}]`;
595
+ if (i >= a.length) entries.push({ path: p, op: "added", newValue: b[i] });
596
+ else if (i >= b.length) entries.push({ path: p, op: "removed", oldValue: a[i] });
597
+ else _walkDiff(a[i], b[i], p, entries);
598
+ }
599
+ } else {
600
+ if (_deepEq(a, b)) entries.push({ path, op: "unchanged", oldValue: a, newValue: b });
601
+ else entries.push({ path, op: "changed", oldValue: a, newValue: b });
602
+ }
603
+ }
604
+ function diffJson(a, b) {
605
+ const entries = [];
606
+ _walkDiff(a, b, "", entries);
607
+ return { entries, added: entries.filter((e) => e.op === "added").length, removed: entries.filter((e) => e.op === "removed").length, changed: entries.filter((e) => e.op === "changed").length, unchanged: entries.filter((e) => e.op === "unchanged").length, totalPaths: entries.length };
608
+ }
609
+ function _stripTags(h) {
610
+ return h.replace(/<[^>]+>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&nbsp;/g, " ").replace(/&quot;/g, '"').trim();
611
+ }
612
+ function _cells(rowHtml, tag) {
613
+ const c = [], re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, "gi");
614
+ let m;
615
+ while ((m = re.exec(rowHtml)) !== null) c.push(_stripTags(m[1]));
616
+ return c;
617
+ }
618
+ function extractTables(html) {
619
+ const tables = [];
620
+ const tr = /<table[^>]*>([\s\S]*?)<\/table>/gi;
621
+ let tm;
622
+ let idx = 0;
623
+ while ((tm = tr.exec(html)) !== null) {
624
+ const body = tm[1], capM = body.match(/<caption[^>]*>([\s\S]*?)<\/caption>/i), caption = capM ? _stripTags(capM[1]) : "";
625
+ let headers = [];
626
+ const theadM = body.match(/<thead[^>]*>([\s\S]*?)<\/thead>/i);
627
+ if (theadM) {
628
+ const r = theadM[1].match(/<tr[^>]*>([\s\S]*?)<\/tr>/i);
629
+ if (r) headers = _cells(r[1], "th").concat(_cells(r[1], "td"));
630
+ }
631
+ if (!headers.length) {
632
+ const f = body.match(/<tr[^>]*>([\s\S]*?)<\/tr>/i);
633
+ if (f) headers = _cells(f[1], "th");
634
+ }
635
+ const rows = [], tbM = body.match(/<tbody[^>]*>([\s\S]*?)<\/tbody>/i), src = tbM ? tbM[1] : body;
636
+ const rr = /<tr[^>]*>([\s\S]*?)<\/tr>/gi;
637
+ let rm;
638
+ while ((rm = rr.exec(src)) !== null) {
639
+ const c = _cells(rm[1], "td");
640
+ if (c.length) rows.push(c);
641
+ }
642
+ tables.push({ index: idx++, caption, headers, rows, rowCount: rows.length, colCount: Math.max(headers.length, ...rows.map((r) => r.length)) });
643
+ }
644
+ return { tables, count: tables.length };
645
+ }
646
+ var _ENT_PATTERNS = [
647
+ { type: "email", re: /\b[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}\b/g },
648
+ { type: "phone", re: /(?<!\d)(\+?1[\s.-]?)?(\(?\d{3}\)?[\s.\-]?\d{3}[\s.\-]?\d{4})(?!\d)/g },
649
+ { type: "date", re: /\b(\d{4}[-\/]\d{2}[-\/]\d{2}|\d{2}[-\/]\d{2}[-\/]\d{4}|(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\.?\s+\d{1,2},?\s+\d{4})\b/gi },
650
+ { type: "ip", re: /\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b/g },
651
+ { type: "creditCard", re: /\b(?:\d{4}[\s\-]?){3}\d{4}\b/g },
652
+ { type: "ssn", re: /\b\d{3}-\d{2}-\d{4}\b/g },
653
+ { type: "iban", re: /\b[A-Z]{2}\d{2}[A-Z0-9]{4}\d{7}([A-Z0-9]?){0,16}\b/g },
654
+ { type: "hashtag", re: /#[a-zA-Z]\w*/g },
655
+ { type: "mention", re: /@[a-zA-Z]\w*/g },
656
+ { type: "currency", re: /(?:USD|EUR|GBP|JPY|CAD|AUD|CHF|CNY|INR)\s*[\d,]+(?:\.\d{1,2})?|[\$£€¥₹]\s*[\d,]+(?:\.\d{1,2})?/g }
657
+ ];
658
+ function extractEntities(text) {
659
+ const entities = [], seen = /* @__PURE__ */ new Set();
660
+ for (const { type, re } of _ENT_PATTERNS) {
661
+ re.lastIndex = 0;
662
+ let m;
663
+ while ((m = re.exec(text)) !== null) {
664
+ const k = `${type}:${m[0]}`;
665
+ if (!seen.has(k)) {
666
+ seen.add(k);
667
+ entities.push({ type, value: m[0].trim(), position: m.index });
668
+ }
669
+ }
670
+ }
671
+ entities.sort((a, b) => a.position - b.position);
672
+ const byType = {};
673
+ for (const e of entities) {
674
+ if (!byType[e.type]) byType[e.type] = [];
675
+ byType[e.type].push(e);
676
+ }
677
+ return { entities, byType, count: entities.length };
678
+ }
679
+ function compressHtml(html, opts = {}) {
680
+ const { removeComments = true, removeScripts = true, removeStyles = true, collapseWhitespace = true } = opts;
681
+ let s = html;
682
+ if (removeScripts) s = s.replace(/<script[\s\S]*?<\/script>/gi, "");
683
+ if (removeStyles) s = s.replace(/<style[\s\S]*?<\/style>/gi, "");
684
+ if (removeComments) s = s.replace(/<!--[\s\S]*?-->/g, "");
685
+ s = s.replace(/\s+data-[a-z\-]+="[^"]*"/gi, "").replace(/\s+class="[^"]*"/gi, "").replace(/\s+style="[^"]*"/gi, "").replace(/\s+id="[^"]*"/gi, "");
686
+ if (collapseWhitespace) {
687
+ s = s.replace(/>\s+</g, "><").replace(/\s{2,}/g, " ").trim();
688
+ }
689
+ const enc = (t) => Buffer.byteLength(t, "utf8");
690
+ const originalBytes = enc(html), compressedBytes = enc(s), originalTokens = Math.ceil(html.length / 4), compressedTokens = Math.ceil(s.length / 4);
691
+ return { compressed: s, originalBytes, compressedBytes, originalTokens, compressedTokens, savings: originalBytes - compressedBytes, savingsPct: originalBytes > 0 ? Math.round((originalBytes - compressedBytes) / originalBytes * 100) : 0 };
692
+ }
693
+ function compressMarkdown(md, opts = {}) {
694
+ const { collapseBlankLines = true, removeComments = true, stripFrontmatter = false, trimLines = true, removeHorizontalRules = false } = opts;
695
+ const changes = [];
696
+ let s = md;
697
+ if (stripFrontmatter) {
698
+ const m = s.match(/^---\n[\s\S]*?\n---\n/);
699
+ if (m) {
700
+ s = s.slice(m[0].length);
701
+ changes.push("Stripped frontmatter");
702
+ }
703
+ }
704
+ if (removeComments) {
705
+ const b = s.length;
706
+ s = s.replace(/<!--[\s\S]*?-->/g, "");
707
+ if (s.length < b) changes.push("Removed HTML comments");
708
+ }
709
+ if (trimLines) {
710
+ const b = s;
711
+ s = s.split("\n").map((l) => l.trimEnd()).join("\n");
712
+ if (s !== b) changes.push("Trimmed trailing whitespace");
713
+ }
714
+ if (collapseBlankLines) {
715
+ const b = s;
716
+ s = s.replace(/\n{3,}/g, "\n\n");
717
+ if (s !== b) changes.push("Collapsed blank lines");
718
+ }
719
+ if (removeHorizontalRules) {
720
+ const b = s;
721
+ s = s.replace(/^[-*_]{3,}\s*$/gm, "");
722
+ if (s !== b) changes.push("Removed horizontal rules");
723
+ }
724
+ const lines = s.split("\n");
725
+ const result = [];
726
+ let inCode = false;
727
+ for (const line of lines) {
728
+ if (line.startsWith("```")) inCode = !inCode;
729
+ if (!inCode && !line.startsWith(" ") && !line.startsWith(" ")) result.push(line.replace(/ {2,}/g, " "));
730
+ else result.push(line);
731
+ }
732
+ s = result.join("\n").trim();
733
+ const ot = Math.ceil(md.length / 4), ct = Math.ceil(s.length / 4);
734
+ return { compressed: s, originalTokens: ot, compressedTokens: ct, savings: ot - ct, savingsPct: ot > 0 ? Math.round((ot - ct) / ot * 100) : 0, changes };
735
+ }
736
+ function _compressVal(val, opts, s) {
737
+ if (val === null) return val;
738
+ if (Array.isArray(val)) {
739
+ return val.map((v) => _compressVal(v, opts, s)).filter((v) => {
740
+ if (opts.removeNulls && v === null) {
741
+ s.nulls++;
742
+ return false;
743
+ }
744
+ if (opts.removeEmptyArrays && Array.isArray(v) && v.length === 0) {
745
+ s.empArr++;
746
+ return false;
747
+ }
748
+ if (opts.removeEmptyObjects && typeof v === "object" && v !== null && !Array.isArray(v) && Object.keys(v).length === 0) {
749
+ s.empObj++;
750
+ return false;
751
+ }
752
+ return true;
753
+ });
754
+ }
755
+ if (typeof val === "object") {
756
+ const obj = val;
757
+ const keys = opts.sortKeys ? Object.keys(obj).sort() : Object.keys(obj);
758
+ const r = {};
759
+ for (const k of keys) {
760
+ const v = _compressVal(obj[k], opts, s);
761
+ if (opts.removeNulls && v === null) {
762
+ s.nulls++;
763
+ continue;
764
+ }
765
+ if (opts.removeEmptyArrays && Array.isArray(v) && v.length === 0) {
766
+ s.empArr++;
767
+ continue;
768
+ }
769
+ if (opts.removeEmptyObjects && typeof v === "object" && v !== null && !Array.isArray(v) && Object.keys(v).length === 0) {
770
+ s.empObj++;
771
+ continue;
772
+ }
773
+ r[k] = v;
774
+ }
775
+ return r;
776
+ }
777
+ return val;
778
+ }
779
+ function compressJson(json, opts = {}) {
780
+ const { removeNulls = true, removeEmptyArrays = true, removeEmptyObjects = true, sortKeys = false } = opts;
781
+ const origStr = JSON.stringify(json);
782
+ const s = { nulls: 0, empArr: 0, empObj: 0 };
783
+ const compressed = _compressVal(json, { removeNulls, removeEmptyArrays, removeEmptyObjects, sortKeys }, s);
784
+ const compStr = JSON.stringify(compressed);
785
+ const ot = Math.ceil(origStr.length / 4), ct = Math.ceil(compStr.length / 4);
786
+ return { compressed: compStr, originalTokens: ot, compressedTokens: ct, savings: ot - ct, savingsPct: ot > 0 ? Math.round((ot - ct) / ot * 100) : 0, removedNulls: s.nulls, removedEmptyArrays: s.empArr, removedEmptyObjects: s.empObj };
787
+ }
788
+ var _RR_STOP = /* @__PURE__ */ new Set(["a", "an", "the", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "by", "is", "are", "was", "were", "be", "been", "have", "has", "had", "it", "this", "that", "these", "those", "i", "we", "you", "he", "she", "they"]);
789
+ function _tok(text) {
790
+ return text.toLowerCase().match(/\b[a-z]{2,}\b/g)?.filter((w) => !_RR_STOP.has(w)) ?? [];
791
+ }
792
+ function rerankChunks(query, chunks, topK) {
793
+ const qt = _tok(query), tc = chunks.map((c) => _tok(c));
794
+ const idf = /* @__PURE__ */ new Map();
795
+ for (const t of qt) {
796
+ const df = tc.filter((c) => c.includes(t)).length;
797
+ idf.set(t, Math.log((chunks.length + 1) / (df + 1)) + 1);
798
+ }
799
+ const ranked = chunks.map((text, index) => {
800
+ const counts = /* @__PURE__ */ new Map();
801
+ for (const t of tc[index]) counts.set(t, (counts.get(t) ?? 0) + 1);
802
+ const max = Math.max(1, ...counts.values());
803
+ let score = 0;
804
+ const matched = [];
805
+ for (const t of qt) {
806
+ const s = (counts.get(t) ?? 0) / max * (idf.get(t) ?? 1);
807
+ if (s > 0) {
808
+ score += s;
809
+ matched.push(t);
810
+ }
811
+ }
812
+ return { index, text, score: Math.round(score * 1e3) / 1e3, matchedTerms: [...new Set(matched)] };
813
+ });
814
+ ranked.sort((a, b) => b.score - a.score);
815
+ return { ranked: topK ? ranked.slice(0, topK) : ranked, query, totalChunks: chunks.length };
816
+ }
817
+ function scoreRelevance(query, passage) {
818
+ const qt = [...new Set(_tok(query))], pt = _tok(passage);
819
+ const counts = /* @__PURE__ */ new Map();
820
+ for (const t of pt) counts.set(t, (counts.get(t) ?? 0) + 1);
821
+ const max = Math.max(1, ...counts.values());
822
+ let score = 0;
823
+ const matched = [];
824
+ for (const t of qt) {
825
+ const c = counts.get(t) ?? 0;
826
+ if (c > 0) {
827
+ score += c / max * (1 / Math.log2(pt.length + 2));
828
+ matched.push(t);
829
+ }
830
+ }
831
+ const coverage = qt.length > 0 ? matched.length / qt.length : 0;
832
+ const norm = Math.min(1, score * qt.length);
833
+ const fs = Math.round((norm * 0.6 + coverage * 0.4) * 100) / 100;
834
+ const grade = fs >= 0.6 ? "high" : fs >= 0.35 ? "medium" : fs > 0 ? "low" : "none";
835
+ return { score: fs, grade, matchedTerms: matched, coverage: Math.round(coverage * 100) / 100 };
836
+ }
837
+ var _SYN = { fast: ["quick", "rapid"], slow: ["sluggish", "delayed"], error: ["bug", "fault", "exception"], fix: ["repair", "resolve", "patch"], search: ["query", "find", "retrieve"], document: ["file", "text", "content"], user: ["customer", "client"], data: ["information", "records"], api: ["endpoint", "service"], build: ["compile", "create"], delete: ["remove", "drop", "erase"], update: ["modify", "edit", "change"], create: ["add", "insert", "generate"], get: ["fetch", "retrieve", "obtain"], list: ["enumerate", "show", "index"], send: ["post", "submit", "transmit"], check: ["verify", "validate", "inspect"], large: ["big", "huge", "massive"], small: ["tiny", "minimal", "compact"], improve: ["enhance", "optimize", "boost"], analyze: ["examine", "evaluate", "assess"], model: ["llm", "ai", "classifier"], token: ["word", "subword", "unit"], chunk: ["segment", "fragment", "block"], embed: ["encode", "vectorize", "represent"], score: ["rank", "rate", "evaluate"], similar: ["related", "relevant", "matching"], agent: ["bot", "assistant", "worker"], memory: ["context", "history", "state"], cost: ["price", "expense", "fee"], deploy: ["release", "ship", "launch"] };
838
+ var _QE_STOP = /* @__PURE__ */ new Set(["a", "an", "the", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "by", "is", "are", "was"]);
839
+ function expandQuery(query, maxSynonyms = 2) {
840
+ const words = query.toLowerCase().match(/\b[a-z]{2,}\b/g) ?? [];
841
+ const added = [];
842
+ const terms = [...words];
843
+ for (const w of words) {
844
+ if (_QE_STOP.has(w)) continue;
845
+ const s = _SYN[w];
846
+ if (s) {
847
+ for (const syn of s.slice(0, maxSynonyms)) if (!terms.includes(syn)) {
848
+ terms.push(syn);
849
+ added.push(syn);
850
+ }
851
+ }
852
+ }
853
+ for (const w of words) {
854
+ let stem = "";
855
+ if (w.endsWith("ing")) stem = w.slice(0, -3);
856
+ else if (w.endsWith("ed")) stem = w.slice(0, -2);
857
+ else if (w.endsWith("s") && !w.endsWith("ss")) stem = w.slice(0, -1);
858
+ else if (w.endsWith("er")) stem = w.slice(0, -2);
859
+ if (stem.length >= 3 && !terms.includes(stem)) {
860
+ terms.push(stem);
861
+ added.push(stem);
862
+ }
863
+ }
864
+ return { original: query, expanded: terms.join(" "), terms, synonymsAdded: added };
865
+ }
866
+ var _SUM_STOP = /* @__PURE__ */ new Set(["a", "an", "the", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "by", "from", "is", "are", "was", "were", "be", "been", "being", "have", "has", "had", "do", "does", "did", "will", "would", "could", "should", "may", "might", "shall", "can", "not", "no", "it", "its", "this", "that", "these", "those", "i", "we", "you", "he", "she", "they", "their", "our", "your", "his", "her"]);
867
+ function _sumTok(text) {
868
+ return text.toLowerCase().match(/\b[a-z]{3,}\b/g)?.filter((w) => !_SUM_STOP.has(w)) ?? [];
869
+ }
870
+ function _splitSents(text) {
871
+ return text.match(/[^.!?\n]+[.!?\n]+(\s|$)|[^.!?\n]+$/g)?.map((s) => s.trim()).filter(Boolean) ?? [text];
872
+ }
873
+ function summarizeForLlm(text, maxTokens = 200, strategy = "extractive") {
874
+ const sents = _splitSents(text);
875
+ const ot = Math.ceil(text.length / 4);
876
+ const tok = (s) => Math.ceil(s.length / 4);
877
+ if (strategy === "first") {
878
+ let sum = "";
879
+ for (const s of sents) {
880
+ const c = sum ? sum + " " + s : s;
881
+ if (tok(c) > maxTokens) break;
882
+ sum = c;
883
+ }
884
+ const st2 = tok(sum);
885
+ return { summary: sum, originalTokens: ot, summaryTokens: st2, compressionRatio: Math.round(st2 / Math.max(1, ot) * 100) / 100, sentencesKept: sum.split(/[.!?]/).filter(Boolean).length, sentencesTotal: sents.length };
886
+ }
887
+ if (strategy === "last") {
888
+ const rev = [...sents].reverse();
889
+ let sum = "";
890
+ for (const s of rev) {
891
+ const c = s + (sum ? " " + sum : "");
892
+ if (tok(c) > maxTokens) break;
893
+ sum = c;
894
+ }
895
+ const st2 = tok(sum);
896
+ return { summary: sum, originalTokens: ot, summaryTokens: st2, compressionRatio: Math.round(st2 / Math.max(1, ot) * 100) / 100, sentencesKept: sum.split(/[.!?]/).filter(Boolean).length, sentencesTotal: sents.length };
897
+ }
898
+ const allTok = _sumTok(text);
899
+ const freq = /* @__PURE__ */ new Map();
900
+ for (const t of allTok) freq.set(t, (freq.get(t) ?? 0) + 1);
901
+ const maxF = Math.max(1, ...freq.values());
902
+ const scored = sents.map((s, i) => {
903
+ const ts = _sumTok(s);
904
+ const sc = ts.reduce((sum, t) => sum + (freq.get(t) ?? 0) / maxF, 0) / Math.max(1, ts.length);
905
+ return { s, i, score: sc + (i === 0 || i === sents.length - 1 ? 0.1 : 0) };
906
+ });
907
+ const picked = [];
908
+ let used = 0;
909
+ for (const { s, i } of [...scored].sort((a, b) => b.score - a.score)) {
910
+ const t = tok(s);
911
+ if (used + t > maxTokens) continue;
912
+ picked.push({ s, i });
913
+ used += t;
914
+ }
915
+ picked.sort((a, b) => a.i - b.i);
916
+ const summary = picked.map((p) => p.s).join(" ");
917
+ const st = tok(summary);
918
+ return { summary, originalTokens: ot, summaryTokens: st, compressionRatio: Math.round(st / Math.max(1, ot) * 100) / 100, sentencesKept: picked.length, sentencesTotal: sents.length };
919
+ }
920
+
921
+ exports.MODEL_PRICING = MODEL_PRICING;
922
+ exports.chunkText = chunkText;
923
+ exports.compressHtml = compressHtml;
924
+ exports.compressJson = compressJson;
925
+ exports.compressMarkdown = compressMarkdown;
926
+ exports.deduplicateLines = deduplicateLines;
927
+ exports.detectPii = detectPii;
928
+ exports.detectPromptInjection = detectPromptInjection;
929
+ exports.detectSecrets = detectSecrets;
930
+ exports.diffJson = diffJson;
931
+ exports.estimateCost = estimateCost;
932
+ exports.estimateTokens = estimateTokens;
933
+ exports.expandQuery = expandQuery;
934
+ exports.extractEntities = extractEntities;
935
+ exports.extractJson = extractJson;
936
+ exports.extractKeywords = extractKeywords;
937
+ exports.extractTables = extractTables;
938
+ exports.extractUrls = extractUrls;
939
+ exports.flattenJson = flattenJson;
940
+ exports.mergeJson = mergeJson;
941
+ exports.redactPii = redactPii;
942
+ exports.repairJson = repairJson;
943
+ exports.rerankChunks = rerankChunks;
944
+ exports.sanitizeHtml = sanitizeHtml;
945
+ exports.scoreRelevance = scoreRelevance;
946
+ exports.summarizeForLlm = summarizeForLlm;
947
+ exports.trimToTokens = trimToTokens;
948
+ exports.unflattenJson = unflattenJson;
949
+ exports.validateJsonSchema = validateJsonSchema;