@ik-firewall/core 1.0.2 → 2.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.
- package/dist/index.cjs +381 -97
- package/dist/index.d.cts +18 -3
- package/dist/index.d.ts +18 -3
- package/dist/index.js +371 -97
- package/package.json +6 -4
- package/scripts/setup-runtime.js +60 -19
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -48,15 +58,18 @@ if (typeof window === "undefined") {
|
|
|
48
58
|
var Registry = class _Registry {
|
|
49
59
|
static instance;
|
|
50
60
|
registryPath;
|
|
61
|
+
usagePath;
|
|
51
62
|
hasWarnedNoFs = false;
|
|
52
63
|
hasWarnedSaveError = false;
|
|
53
64
|
constructor() {
|
|
54
65
|
this.registryPath = "";
|
|
66
|
+
this.usagePath = "";
|
|
55
67
|
if (typeof process !== "undefined" && process.versions && process.versions.node) {
|
|
56
68
|
try {
|
|
57
69
|
fs = require("fs");
|
|
58
70
|
path = require("path");
|
|
59
71
|
this.registryPath = path.join(process.cwd(), ".ik-adapter", "registry.json");
|
|
72
|
+
this.usagePath = path.join(process.cwd(), ".ik-adapter", "usage.json");
|
|
60
73
|
this.ensureDirectory();
|
|
61
74
|
} catch (e) {
|
|
62
75
|
if (!this.hasWarnedNoFs) {
|
|
@@ -91,8 +104,13 @@ var Registry = class _Registry {
|
|
|
91
104
|
if (!fs || !this.registryPath) return {};
|
|
92
105
|
try {
|
|
93
106
|
if (fs.existsSync(this.registryPath)) {
|
|
94
|
-
const
|
|
95
|
-
|
|
107
|
+
const rawData = fs.readFileSync(this.registryPath, "utf8");
|
|
108
|
+
try {
|
|
109
|
+
const decoded = Buffer.from(rawData, "base64").toString("utf8");
|
|
110
|
+
return JSON.parse(decoded);
|
|
111
|
+
} catch (e) {
|
|
112
|
+
return JSON.parse(rawData);
|
|
113
|
+
}
|
|
96
114
|
}
|
|
97
115
|
} catch (error) {
|
|
98
116
|
console.error("[IK_REGISTRY] Load error:", error);
|
|
@@ -112,7 +130,9 @@ var Registry = class _Registry {
|
|
|
112
130
|
}
|
|
113
131
|
try {
|
|
114
132
|
this.ensureDirectory();
|
|
115
|
-
|
|
133
|
+
const jsonStr = JSON.stringify(data, null, 2);
|
|
134
|
+
const obfuscated = Buffer.from(jsonStr).toString("base64");
|
|
135
|
+
fs.writeFileSync(this.registryPath, obfuscated, "utf8");
|
|
116
136
|
} catch (error) {
|
|
117
137
|
if (!this.hasWarnedSaveError) {
|
|
118
138
|
console.error("\n[IK_REGISTRY] \u{1F6A8} ERROR saving registry.json. Your environment likely has a read-only filesystem (e.g. Vercel Serverless).");
|
|
@@ -143,17 +163,76 @@ var Registry = class _Registry {
|
|
|
143
163
|
delete all[instanceId];
|
|
144
164
|
this.save(all);
|
|
145
165
|
}
|
|
166
|
+
getUsageFilePath() {
|
|
167
|
+
return this.usagePath;
|
|
168
|
+
}
|
|
169
|
+
exists(filePath) {
|
|
170
|
+
return fs && fs.existsSync(filePath);
|
|
171
|
+
}
|
|
172
|
+
loadUsage() {
|
|
173
|
+
if (!fs || !this.usagePath || !fs.existsSync(this.usagePath)) return [];
|
|
174
|
+
try {
|
|
175
|
+
const raw = fs.readFileSync(this.usagePath, "utf8");
|
|
176
|
+
return JSON.parse(raw);
|
|
177
|
+
} catch (e) {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
saveUsage(data) {
|
|
182
|
+
if (!fs || !this.usagePath) return;
|
|
183
|
+
try {
|
|
184
|
+
fs.writeFileSync(this.usagePath, JSON.stringify(data, null, 2), "utf8");
|
|
185
|
+
} catch (e) {
|
|
186
|
+
console.error("[IK_REGISTRY] Failed to save usage:", e);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
clearUsage() {
|
|
190
|
+
if (!fs || !this.usagePath) return;
|
|
191
|
+
try {
|
|
192
|
+
if (fs.existsSync(this.usagePath)) {
|
|
193
|
+
fs.unlinkSync(this.usagePath);
|
|
194
|
+
}
|
|
195
|
+
} catch (e) {
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// src/lib/hmac.ts
|
|
201
|
+
var import_node_crypto = __toESM(require("crypto"), 1);
|
|
202
|
+
var IKHmac = class {
|
|
203
|
+
secret;
|
|
204
|
+
constructor(secret) {
|
|
205
|
+
this.secret = secret;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Verifies that a response from the IK-Firewall server is authentic.
|
|
209
|
+
*/
|
|
210
|
+
verify(data) {
|
|
211
|
+
if (!data || !data.signature) return false;
|
|
212
|
+
const { signature, ...rest } = data;
|
|
213
|
+
const payload = JSON.stringify(rest);
|
|
214
|
+
const expectedSignature = import_node_crypto.default.createHmac("sha256", this.secret).update(payload).digest("hex");
|
|
215
|
+
return signature === expectedSignature;
|
|
216
|
+
}
|
|
146
217
|
};
|
|
147
218
|
|
|
148
219
|
// src/ConfigManager.ts
|
|
149
220
|
var ConfigManager = class {
|
|
150
221
|
config;
|
|
151
222
|
registry;
|
|
152
|
-
|
|
223
|
+
hooks;
|
|
224
|
+
constructor(initialConfig, hooks) {
|
|
153
225
|
this.config = initialConfig;
|
|
226
|
+
this.hooks = hooks;
|
|
154
227
|
this.registry = Registry.getInstance();
|
|
155
228
|
this.loadFromRegistry();
|
|
156
229
|
}
|
|
230
|
+
getRegistry() {
|
|
231
|
+
return this.registry;
|
|
232
|
+
}
|
|
233
|
+
setHooks(hooks) {
|
|
234
|
+
this.hooks = hooks;
|
|
235
|
+
}
|
|
157
236
|
loadFromRegistry() {
|
|
158
237
|
const instanceConfigs = this.registry.load();
|
|
159
238
|
this.config.instanceConfigs = {
|
|
@@ -161,6 +240,76 @@ var ConfigManager = class {
|
|
|
161
240
|
...instanceConfigs
|
|
162
241
|
};
|
|
163
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Syncs remote configuration (pricing, license status) from the Vercel API.
|
|
245
|
+
* Runs once every 24 hours (Heartbeat).
|
|
246
|
+
*/
|
|
247
|
+
async syncRemoteConfig(instanceId) {
|
|
248
|
+
const now = /* @__PURE__ */ new Date();
|
|
249
|
+
const instanceConfig = this.config.instanceConfigs?.[instanceId];
|
|
250
|
+
if (instanceConfig?.lastSyncDate) {
|
|
251
|
+
const lastSync = new Date(instanceConfig.lastSyncDate);
|
|
252
|
+
const hoursSinceSync = (now.getTime() - lastSync.getTime()) / (1e3 * 60 * 60);
|
|
253
|
+
if (hoursSinceSync < 24) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
const endpoint = this.config.centralReportEndpoint || "https://ik-firewall.vercel.app/api/v1/sync";
|
|
259
|
+
const instanceConfig2 = this.config.instanceConfigs?.[instanceId];
|
|
260
|
+
const registrationEmail = instanceConfig2?.ownerEmail || this.config.ownerEmail;
|
|
261
|
+
if ((!instanceConfig2?.licenseKey || instanceConfig2.licenseKey.startsWith("TRIAL-")) && registrationEmail) {
|
|
262
|
+
const success = await this.registerInstance(instanceId, registrationEmail);
|
|
263
|
+
if (success) {
|
|
264
|
+
this.hooks?.onStatus?.("\u2728 IK_SYNC: Zero-Config registration successful. License issued.");
|
|
265
|
+
return;
|
|
266
|
+
} else {
|
|
267
|
+
this.hooks?.onStatus?.("\u26A0\uFE0F IK_SYNC: Auto-registration failed. Continuing with local trial.");
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
this.hooks?.onStatus?.(`\u{1F4E1} IK_SYNC: Heartbeat to ${endpoint}...`);
|
|
271
|
+
const response = await fetch(`${endpoint}?licenseKey=${instanceConfig2?.licenseKey || ""}&instanceId=${instanceId}&version=2.2.0`);
|
|
272
|
+
if (response.ok) {
|
|
273
|
+
const data = await response.json();
|
|
274
|
+
if (this.config.hmacSecret) {
|
|
275
|
+
const verifier = new IKHmac(this.config.hmacSecret);
|
|
276
|
+
if (!verifier.verify(data)) {
|
|
277
|
+
console.error("[IK_SYNC] \u{1F6A8} Signature verification failed! Remote config rejected.");
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
this.setInstanceConfig(instanceId, {
|
|
282
|
+
billingStatus: data.billingStatus,
|
|
283
|
+
isVerified: data.isVerified,
|
|
284
|
+
pricing: data.pricing,
|
|
285
|
+
lastSyncDate: now.toISOString()
|
|
286
|
+
});
|
|
287
|
+
this.hooks?.onStatus?.("\u2705 IK_SYNC: Remote configuration updated and verified.");
|
|
288
|
+
} else {
|
|
289
|
+
throw new Error(`Server responded with ${response.status}`);
|
|
290
|
+
}
|
|
291
|
+
if (!instanceConfig2?.firstUseDate) {
|
|
292
|
+
this.setInstanceConfig(instanceId, { firstUseDate: now.toISOString() });
|
|
293
|
+
}
|
|
294
|
+
} catch (error) {
|
|
295
|
+
console.error(`[IK_CONFIG] Remote sync failed (${error.message}). Falling open (Fail-Open mode).`);
|
|
296
|
+
this.setInstanceConfig(instanceId, {
|
|
297
|
+
isVerified: true,
|
|
298
|
+
lastSyncDate: now.toISOString()
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
const registry = this.registry;
|
|
303
|
+
if (registry.exists(registry.getUsageFilePath())) {
|
|
304
|
+
const aggregatedUsage = registry.loadUsage();
|
|
305
|
+
if (aggregatedUsage.length > 0) {
|
|
306
|
+
this.hooks?.onStatus?.(`\u{1F4CA} IK_SYNC: Found ${aggregatedUsage.length} pending usage reports. Preparation for Daily Flush...`);
|
|
307
|
+
this._pendingUsage = aggregatedUsage;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
} catch (e) {
|
|
311
|
+
}
|
|
312
|
+
}
|
|
164
313
|
getConfig() {
|
|
165
314
|
return { ...this.config };
|
|
166
315
|
}
|
|
@@ -200,6 +349,40 @@ var ConfigManager = class {
|
|
|
200
349
|
}
|
|
201
350
|
this.updateConfig({ plugins });
|
|
202
351
|
}
|
|
352
|
+
async registerInstance(instanceId, email) {
|
|
353
|
+
try {
|
|
354
|
+
const endpoint = (this.config.centralReportEndpoint || "https://ik-firewall.vercel.app/api/v1").replace(/\/sync$/, "") + "/register";
|
|
355
|
+
this.hooks?.onStatus?.(`\u{1F680} IK_REGISTRY: Registering new instance for ${email}...`);
|
|
356
|
+
const response = await fetch(endpoint, {
|
|
357
|
+
method: "POST",
|
|
358
|
+
headers: { "Content-Type": "application/json" },
|
|
359
|
+
body: JSON.stringify({ email, instanceId })
|
|
360
|
+
});
|
|
361
|
+
if (response.ok) {
|
|
362
|
+
const data = await response.json();
|
|
363
|
+
if (this.config.hmacSecret) {
|
|
364
|
+
const verifier = new IKHmac(this.config.hmacSecret);
|
|
365
|
+
if (!verifier.verify(data)) {
|
|
366
|
+
console.error("[IK_REGISTRY] \u{1F6A8} Registration signature mismatch. Data rejected!");
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
this.setInstanceConfig(instanceId, {
|
|
371
|
+
licenseKey: data.licenseKey,
|
|
372
|
+
billingStatus: data.status.toLowerCase(),
|
|
373
|
+
isVerified: true,
|
|
374
|
+
firstUseDate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
375
|
+
pricing: data.pricing
|
|
376
|
+
});
|
|
377
|
+
this.hooks?.onStatus?.("\u2728 IK_REGISTRY: Instance registered successfully and license issued.");
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
return false;
|
|
381
|
+
} catch (error) {
|
|
382
|
+
console.error("[IK_REGISTRY] Registration failed:", error);
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
203
386
|
ensureInstanceConfig(instanceId) {
|
|
204
387
|
const all = this.registry.load();
|
|
205
388
|
if (!all[instanceId]) {
|
|
@@ -207,7 +390,9 @@ var ConfigManager = class {
|
|
|
207
390
|
balance: this.config.balance || 0.5,
|
|
208
391
|
aggressiveness: this.config.aggressiveness || 0.5,
|
|
209
392
|
isEnabled: true,
|
|
210
|
-
contextMode: "FIRST_MESSAGE"
|
|
393
|
+
contextMode: "FIRST_MESSAGE",
|
|
394
|
+
ownerEmail: this.config.ownerEmail
|
|
395
|
+
// Inherit from global if not specific
|
|
211
396
|
});
|
|
212
397
|
this.loadFromRegistry();
|
|
213
398
|
}
|
|
@@ -1280,18 +1465,12 @@ var Orchestrator = class {
|
|
|
1280
1465
|
};
|
|
1281
1466
|
|
|
1282
1467
|
// src/UsageTracker.ts
|
|
1283
|
-
var UsageTracker = class
|
|
1468
|
+
var UsageTracker = class {
|
|
1284
1469
|
buffer = [];
|
|
1285
1470
|
MAX_BUFFER_SIZE = 1;
|
|
1286
1471
|
// Immediate reporting for Edge/Stateless environments
|
|
1287
1472
|
hooks;
|
|
1288
1473
|
config;
|
|
1289
|
-
// Static pricing benchmarks (cost per 1k tokens in USD)
|
|
1290
|
-
static PRICING = {
|
|
1291
|
-
"gpt-4o": { input: 5e-3, output: 0.015 },
|
|
1292
|
-
"gpt-4o-mini": { input: 15e-5, output: 6e-4 },
|
|
1293
|
-
"local": { input: 0, output: 0 }
|
|
1294
|
-
};
|
|
1295
1474
|
constructor(config, hooks) {
|
|
1296
1475
|
this.config = config;
|
|
1297
1476
|
this.hooks = hooks;
|
|
@@ -1302,8 +1481,10 @@ var UsageTracker = class _UsageTracker {
|
|
|
1302
1481
|
async logInteraction(params) {
|
|
1303
1482
|
const { instanceId, model, inputTokens, outputTokens, optimizedTokens = 0, cqScore, routingPath, clientOrigin, trace } = params;
|
|
1304
1483
|
const tokensSaved = optimizedTokens > 0 ? inputTokens - optimizedTokens : 0;
|
|
1305
|
-
const
|
|
1306
|
-
const
|
|
1484
|
+
const instanceConfig = this.config.getConfig().instanceConfigs?.[instanceId];
|
|
1485
|
+
const remotePricing = instanceConfig?.pricing?.[model] || { input: 5e-3, output: 0.015 };
|
|
1486
|
+
const costSaved = tokensSaved / 1e3 * remotePricing.input;
|
|
1487
|
+
const commissionDue = costSaved * 0.2;
|
|
1307
1488
|
const data = {
|
|
1308
1489
|
instanceId,
|
|
1309
1490
|
model_used: model,
|
|
@@ -1313,6 +1494,7 @@ var UsageTracker = class _UsageTracker {
|
|
|
1313
1494
|
optimized_tokens: optimizedTokens,
|
|
1314
1495
|
tokens_saved: tokensSaved,
|
|
1315
1496
|
cost_saved: Number(costSaved.toFixed(6)),
|
|
1497
|
+
commission_due: Number(commissionDue.toFixed(6)),
|
|
1316
1498
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1317
1499
|
is_local: model === "local",
|
|
1318
1500
|
cq_score: cqScore,
|
|
@@ -1321,40 +1503,71 @@ var UsageTracker = class _UsageTracker {
|
|
|
1321
1503
|
};
|
|
1322
1504
|
this.buffer.push(data);
|
|
1323
1505
|
this.hooks?.onUsageReported?.(data);
|
|
1324
|
-
|
|
1325
|
-
|
|
1506
|
+
await this.persistToLocalBuffer(data);
|
|
1507
|
+
}
|
|
1508
|
+
/**
|
|
1509
|
+
* Appends usage data to the local .ik-adapter/usage.json file
|
|
1510
|
+
*/
|
|
1511
|
+
async persistToLocalBuffer(data) {
|
|
1512
|
+
try {
|
|
1513
|
+
const registry = this.config.getRegistry();
|
|
1514
|
+
const usageFile = registry.getUsageFilePath();
|
|
1515
|
+
let existingUsage = [];
|
|
1516
|
+
if (registry.exists(usageFile)) {
|
|
1517
|
+
existingUsage = registry.loadUsage();
|
|
1518
|
+
}
|
|
1519
|
+
existingUsage.push(data);
|
|
1520
|
+
registry.saveUsage(existingUsage);
|
|
1521
|
+
} catch (e) {
|
|
1522
|
+
console.error("[IK_TRACKER] Failed to persist usage to local buffer:", e);
|
|
1326
1523
|
}
|
|
1327
1524
|
}
|
|
1328
1525
|
/**
|
|
1329
1526
|
* Send buffered usage data to the central IK billing service
|
|
1330
1527
|
*/
|
|
1331
|
-
async flush() {
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
this.buffer = [];
|
|
1337
|
-
return;
|
|
1338
|
-
}
|
|
1528
|
+
async flush(aggregatedData) {
|
|
1529
|
+
const usageToFlush = aggregatedData || this.buffer;
|
|
1530
|
+
if (usageToFlush.length === 0) return;
|
|
1531
|
+
const endpoint = this.config.get("centralReportEndpoint") || "https://ik-firewall.vercel.app/api/v1/report";
|
|
1532
|
+
const hmacSecret = this.config.get("hmacSecret");
|
|
1339
1533
|
try {
|
|
1340
|
-
|
|
1341
|
-
|
|
1534
|
+
this.hooks?.onStatus?.(`\u{1F4E4} IK_TRACKER: Flushing ${usageToFlush.length} interactions to ${endpoint}...`);
|
|
1535
|
+
const reportsByInstance = {};
|
|
1536
|
+
for (const report of usageToFlush) {
|
|
1537
|
+
if (!reportsByInstance[report.instanceId]) reportsByInstance[report.instanceId] = [];
|
|
1538
|
+
reportsByInstance[report.instanceId].push(report);
|
|
1539
|
+
}
|
|
1540
|
+
for (const [instanceId, reports] of Object.entries(reportsByInstance)) {
|
|
1541
|
+
const instanceConfig = this.config.getConfig().instanceConfigs?.[instanceId];
|
|
1542
|
+
const payload = {
|
|
1543
|
+
licenseKey: instanceConfig?.licenseKey || "",
|
|
1544
|
+
instanceId,
|
|
1545
|
+
reports: reports.map((r) => ({
|
|
1546
|
+
modelName: r.model_used,
|
|
1547
|
+
tokensIn: r.input_tokens,
|
|
1548
|
+
tokensOut: r.output_tokens,
|
|
1549
|
+
tokensSaved: r.tokens_saved,
|
|
1550
|
+
date: r.timestamp
|
|
1551
|
+
}))
|
|
1552
|
+
};
|
|
1553
|
+
if (hmacSecret) {
|
|
1554
|
+
const jsonStr = JSON.stringify(payload);
|
|
1555
|
+
const crypto2 = await import("crypto");
|
|
1556
|
+
payload.signature = crypto2.createHmac("sha256", hmacSecret).update(jsonStr).digest("hex");
|
|
1557
|
+
}
|
|
1558
|
+
const response = await fetch(endpoint, {
|
|
1342
1559
|
method: "POST",
|
|
1343
1560
|
headers: { "Content-Type": "application/json" },
|
|
1344
|
-
body: JSON.stringify(
|
|
1345
|
-
instanceId: report.instanceId,
|
|
1346
|
-
model: report.model_used,
|
|
1347
|
-
inputTokens: report.input_tokens,
|
|
1348
|
-
outputTokens: report.output_tokens,
|
|
1349
|
-
optimizedTokens: report.optimized_tokens,
|
|
1350
|
-
cqScore: report.cq_score,
|
|
1351
|
-
routingPath: report.routingPath,
|
|
1352
|
-
clientOrigin: report.clientOrigin,
|
|
1353
|
-
trace: report.trace
|
|
1354
|
-
})
|
|
1561
|
+
body: JSON.stringify(payload)
|
|
1355
1562
|
});
|
|
1563
|
+
if (!response.ok) {
|
|
1564
|
+
console.error(`[IK_TRACKER] Failed to send aggregate for ${instanceId}: ${response.statusText}`);
|
|
1565
|
+
}
|
|
1356
1566
|
}
|
|
1357
1567
|
this.buffer = [];
|
|
1568
|
+
if (aggregatedData) {
|
|
1569
|
+
this.config.getRegistry().clearUsage();
|
|
1570
|
+
}
|
|
1358
1571
|
} catch (error) {
|
|
1359
1572
|
console.error("[IK_TRACKER] Failed to flush usage data:", error);
|
|
1360
1573
|
}
|
|
@@ -1364,6 +1577,49 @@ var UsageTracker = class _UsageTracker {
|
|
|
1364
1577
|
}
|
|
1365
1578
|
};
|
|
1366
1579
|
|
|
1580
|
+
// src/CognitiveEngine.ts
|
|
1581
|
+
var CognitiveEngine = class {
|
|
1582
|
+
/**
|
|
1583
|
+
* Sniff Cognitive DNA (CQ Score: 0-100)
|
|
1584
|
+
* Higher score = higher quality, human-like, structured content.
|
|
1585
|
+
*/
|
|
1586
|
+
static sniffDNA(input) {
|
|
1587
|
+
if (!input || input.trim().length === 0) return 0;
|
|
1588
|
+
const length = input.length;
|
|
1589
|
+
const words = input.split(/\s+/).filter((w) => w.length > 0);
|
|
1590
|
+
const wordCount = words.length;
|
|
1591
|
+
const sentences = input.split(/[.!?]+/).filter((s) => s.trim().length > 0);
|
|
1592
|
+
const avgSentenceLength = sentences.length > 0 ? wordCount / sentences.length : 0;
|
|
1593
|
+
const sentenceVariety = sentences.length > 1 ? 10 : 0;
|
|
1594
|
+
const uniqueWords = new Set(words.map((w) => w.toLowerCase())).size;
|
|
1595
|
+
const lexicalBreadth = wordCount > 0 ? uniqueWords / wordCount * 40 : 0;
|
|
1596
|
+
const markers = (input.match(/[,;:"'()\[\]]/g) || []).length;
|
|
1597
|
+
const structuralHealth = Math.min(markers / wordCount * 100, 20);
|
|
1598
|
+
const repetitivePenalty = this.calculateRepetitivePenalty(input);
|
|
1599
|
+
let score = lexicalBreadth + structuralHealth + sentenceVariety + (avgSentenceLength > 5 ? 20 : 0);
|
|
1600
|
+
score -= repetitivePenalty;
|
|
1601
|
+
return Math.max(0, Math.min(100, score));
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Calculate Crystallization Threshold
|
|
1605
|
+
* Logic:
|
|
1606
|
+
* Low CQ (Noise) -> Aggressive optimization (Lower threshold, e.g. 0.6)
|
|
1607
|
+
* High CQ (Quality) -> Conservative optimization (Higher threshold, e.g. 0.9)
|
|
1608
|
+
*/
|
|
1609
|
+
static calculateThreshold(cqScore) {
|
|
1610
|
+
if (cqScore < 30) return 0.65;
|
|
1611
|
+
if (cqScore < 60) return 0.78;
|
|
1612
|
+
if (cqScore < 85) return 0.88;
|
|
1613
|
+
return 0.95;
|
|
1614
|
+
}
|
|
1615
|
+
static calculateRepetitivePenalty(input) {
|
|
1616
|
+
const chunks = input.match(/.{1,10}/g) || [];
|
|
1617
|
+
const uniqueChunks = new Set(chunks).size;
|
|
1618
|
+
const ratio = chunks.length > 10 ? uniqueChunks / chunks.length : 1;
|
|
1619
|
+
return ratio < 0.5 ? (1 - ratio) * 50 : 0;
|
|
1620
|
+
}
|
|
1621
|
+
};
|
|
1622
|
+
|
|
1367
1623
|
// src/core.ts
|
|
1368
1624
|
var IKFirewallCore = class _IKFirewallCore {
|
|
1369
1625
|
static instance;
|
|
@@ -1418,55 +1674,75 @@ var IKFirewallCore = class _IKFirewallCore {
|
|
|
1418
1674
|
FLUID_THRESHOLD: 4
|
|
1419
1675
|
}
|
|
1420
1676
|
};
|
|
1421
|
-
gatekeeper
|
|
1677
|
+
gatekeeper;
|
|
1422
1678
|
configManager;
|
|
1423
1679
|
sessionDNA = 0;
|
|
1424
|
-
hooks;
|
|
1425
1680
|
cloudProvider;
|
|
1426
1681
|
localProvider;
|
|
1427
1682
|
orchestrator;
|
|
1428
1683
|
usageTracker;
|
|
1684
|
+
_hooks;
|
|
1685
|
+
defaultConfig;
|
|
1429
1686
|
constructor(config, hooks, cloudProvider) {
|
|
1430
|
-
|
|
1687
|
+
this.defaultConfig = {
|
|
1431
1688
|
aggressiveness: 0.15,
|
|
1432
1689
|
forcedEfficiency: false,
|
|
1433
1690
|
precisionBias: _IKFirewallCore.CONSTANTS.PRECISION.BIAS_DEFAULT,
|
|
1434
1691
|
balance: _IKFirewallCore.CONSTANTS.TONE.STABLE_VAL,
|
|
1435
1692
|
gatekeeperEnabled: true,
|
|
1436
1693
|
locale: "en",
|
|
1437
|
-
providerMode: "hybrid",
|
|
1694
|
+
providerMode: process.env.IK_FIREWALL_MODE || "hybrid",
|
|
1438
1695
|
runtimeStrategy: "internal",
|
|
1439
|
-
localAuditEndpoint: "http://127.0.0.1:8085/v1/chat/completions",
|
|
1440
|
-
|
|
1696
|
+
localAuditEndpoint: process.env.IK_FIREWALL_LOCAL_ENDPOINT || "http://127.0.0.1:8085/v1/chat/completions",
|
|
1697
|
+
centralReportEndpoint: process.env.IK_FIREWALL_CENTRAL_ENDPOINT || "https://ik-firewall.vercel.app/api/v1/sync",
|
|
1698
|
+
ownerEmail: process.env.IK_FIREWALL_EMAIL,
|
|
1699
|
+
hmacSecret: process.env.IK_FIREWALL_SECRET,
|
|
1700
|
+
fallbackToCloudAudit: process.env.IK_FIREWALL_FALLBACK === "true" || false,
|
|
1441
1701
|
modelConfig: {
|
|
1442
1702
|
modelType: "1b"
|
|
1443
1703
|
},
|
|
1444
1704
|
plugins: []
|
|
1445
1705
|
};
|
|
1446
|
-
this.
|
|
1447
|
-
|
|
1706
|
+
this._hooks = hooks;
|
|
1707
|
+
this.configManager = new ConfigManager({ ...this.defaultConfig, ...config }, hooks);
|
|
1708
|
+
this.gatekeeper = new HeuristicGatekeeper();
|
|
1709
|
+
this.sessionDNA = _IKFirewallCore.CONSTANTS.DNA.MIN;
|
|
1448
1710
|
const activeConfig = this.configManager.getConfig();
|
|
1449
1711
|
this.localProvider = new LocalProvider(activeConfig.localAuditEndpoint);
|
|
1450
1712
|
if (cloudProvider) {
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
const
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1713
|
+
this.cloudProvider = cloudProvider instanceof BaseProvider ? cloudProvider : new OpenAIProvider(cloudProvider);
|
|
1714
|
+
} else if (process.env.OPENAI_API_KEY) {
|
|
1715
|
+
this.cloudProvider = new OpenAIProvider(async (prompt, role) => {
|
|
1716
|
+
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
1717
|
+
method: "POST",
|
|
1718
|
+
headers: {
|
|
1719
|
+
"Authorization": `Bearer ${process.env.OPENAI_API_KEY}`,
|
|
1720
|
+
"Content-Type": "application/json"
|
|
1721
|
+
},
|
|
1722
|
+
body: JSON.stringify({
|
|
1723
|
+
model: role === "audit_layer" ? "gpt-4o-mini" : "gpt-4o",
|
|
1724
|
+
messages: [{ role: "user", content: prompt }],
|
|
1725
|
+
temperature: 0.1
|
|
1726
|
+
})
|
|
1727
|
+
});
|
|
1728
|
+
if (!response.ok) throw new Error(`OpenAI API Error: ${response.statusText}`);
|
|
1729
|
+
const data = await response.json();
|
|
1730
|
+
return {
|
|
1731
|
+
content: data.choices[0].message.content,
|
|
1732
|
+
usage: data.usage
|
|
1733
|
+
};
|
|
1734
|
+
});
|
|
1467
1735
|
}
|
|
1468
1736
|
this.orchestrator = new Orchestrator(this.configManager);
|
|
1469
|
-
this.usageTracker = new UsageTracker(this.configManager, this.
|
|
1737
|
+
this.usageTracker = new UsageTracker(this.configManager, this._hooks);
|
|
1738
|
+
}
|
|
1739
|
+
get hooks() {
|
|
1740
|
+
return this._hooks;
|
|
1741
|
+
}
|
|
1742
|
+
set hooks(newHooks) {
|
|
1743
|
+
this._hooks = newHooks;
|
|
1744
|
+
this.configManager.setHooks(newHooks);
|
|
1745
|
+
this.usageTracker.hooks = newHooks;
|
|
1470
1746
|
}
|
|
1471
1747
|
static getInstance(config, hooks) {
|
|
1472
1748
|
if (!_IKFirewallCore.instance) {
|
|
@@ -1578,6 +1854,21 @@ var IKFirewallCore = class _IKFirewallCore {
|
|
|
1578
1854
|
async analyze(input, provider, personaName = "professional", locale, instanceId) {
|
|
1579
1855
|
if (instanceId) {
|
|
1580
1856
|
this.configManager.ensureInstanceConfig(instanceId);
|
|
1857
|
+
const syncPromise = this.configManager.syncRemoteConfig(instanceId).then(async () => {
|
|
1858
|
+
const pendingUsage = this.configManager._pendingUsage;
|
|
1859
|
+
if (pendingUsage && pendingUsage.length > 0) {
|
|
1860
|
+
await this.usageTracker.flush(pendingUsage);
|
|
1861
|
+
this.configManager._pendingUsage = null;
|
|
1862
|
+
}
|
|
1863
|
+
});
|
|
1864
|
+
await syncPromise;
|
|
1865
|
+
}
|
|
1866
|
+
const mergedConfig = this.configManager.getMergedConfig(instanceId);
|
|
1867
|
+
if (instanceId && mergedConfig?.isVerified === false) {
|
|
1868
|
+
if (mergedConfig?.billingStatus === "blocked") {
|
|
1869
|
+
this.hooks?.onStatus?.("\u26A0\uFE0F [IK_FIREWALL] Subscription inactive. Optimization bypassed (Fail-Safe mode).");
|
|
1870
|
+
return this.getEmptyMetrics();
|
|
1871
|
+
}
|
|
1581
1872
|
}
|
|
1582
1873
|
let auditProvider = this.localProvider;
|
|
1583
1874
|
let overrideProvider = null;
|
|
@@ -1612,18 +1903,20 @@ var IKFirewallCore = class _IKFirewallCore {
|
|
|
1612
1903
|
const ei = (input.match(/[,;.:-]/g) || []).length / (input.split(" ").length || 1);
|
|
1613
1904
|
let requiresAuditOverride = false;
|
|
1614
1905
|
let boundaryMetrics = { boundaryScore: 0, requiresHumanIntervention: false };
|
|
1615
|
-
if (this.getConfig()
|
|
1616
|
-
boundaryMetrics = this.gatekeeper.evaluateBoundaryCommunication(input, this.getConfig()
|
|
1617
|
-
if (boundaryMetrics
|
|
1906
|
+
if (this.getConfig()?.safeMode) {
|
|
1907
|
+
boundaryMetrics = this.gatekeeper.evaluateBoundaryCommunication(input, this.getConfig()?.customBoundaryKeywords);
|
|
1908
|
+
if (boundaryMetrics?.requiresHumanIntervention) {
|
|
1618
1909
|
requiresAuditOverride = true;
|
|
1619
1910
|
}
|
|
1620
1911
|
}
|
|
1621
1912
|
const skipAudit = !requiresAuditOverride && this.getConfig().gatekeeperEnabled && localInsight.complexityScore < _IKFirewallCore.CONSTANTS.HEURISTICS.COMPLEXITY_SKIP_THRESHOLD;
|
|
1622
1913
|
if (skipAudit) {
|
|
1623
1914
|
this.hooks?.onStatus?.("\u26A1 IK_GATEKEEPER: Simple request detected. Skipping Deep Audit.");
|
|
1624
|
-
const metrics2 = this.createHeuristicMetrics(input, dm, ei, uniqueWords, localInsight
|
|
1625
|
-
metrics2.semanticInsights
|
|
1626
|
-
|
|
1915
|
+
const metrics2 = this.createHeuristicMetrics(input, dm, ei, uniqueWords, localInsight?.intentMode);
|
|
1916
|
+
if (metrics2.semanticInsights) {
|
|
1917
|
+
metrics2.semanticInsights.boundaryScore = boundaryMetrics.boundaryScore;
|
|
1918
|
+
metrics2.semanticInsights.requiresHumanIntervention = boundaryMetrics.requiresHumanIntervention;
|
|
1919
|
+
}
|
|
1627
1920
|
this.hooks?.onAuditComplete?.(metrics2);
|
|
1628
1921
|
return metrics2;
|
|
1629
1922
|
}
|
|
@@ -1638,10 +1931,17 @@ var IKFirewallCore = class _IKFirewallCore {
|
|
|
1638
1931
|
throw e;
|
|
1639
1932
|
}
|
|
1640
1933
|
}
|
|
1641
|
-
|
|
1934
|
+
const cqScore = CognitiveEngine.sniffDNA(input);
|
|
1935
|
+
metrics.dm = Math.max(metrics.dm, cqScore / 10);
|
|
1936
|
+
this.applyHardening(input, metrics, activeLocale);
|
|
1642
1937
|
this.hooks?.onAuditComplete?.(metrics);
|
|
1643
1938
|
return metrics;
|
|
1644
1939
|
}
|
|
1940
|
+
applyHardening(input, metrics, locale = "en") {
|
|
1941
|
+
const archetype = metrics.semanticInsights?.archetype || "DYNAMIC";
|
|
1942
|
+
const domain = metrics.semanticInsights?.detectedDomain || "GENERAL";
|
|
1943
|
+
this.setSessionDNA(this.sessionDNA + 5);
|
|
1944
|
+
}
|
|
1645
1945
|
getEmptyMetrics() {
|
|
1646
1946
|
return {
|
|
1647
1947
|
dm: 0,
|
|
@@ -1693,39 +1993,17 @@ var IKFirewallCore = class _IKFirewallCore {
|
|
|
1693
1993
|
inputLength: input.length
|
|
1694
1994
|
};
|
|
1695
1995
|
}
|
|
1696
|
-
sniffDNA(input, metrics, locale = "en") {
|
|
1697
|
-
const archetype = metrics.semanticInsights?.archetype || "DYNAMIC";
|
|
1698
|
-
const domain = metrics.semanticInsights?.detectedDomain || "GENERAL";
|
|
1699
|
-
const meta = Dictionaries.meta[locale] || Dictionaries.meta.en;
|
|
1700
|
-
const patterns = (meta.system_prompt_patterns || "").split(",").map((p) => p.trim()).filter((p) => p.length > 0);
|
|
1701
|
-
const isSystemPrompt = patterns.some((kw) => input.toLowerCase().includes(kw));
|
|
1702
|
-
let dnaShift = 0;
|
|
1703
|
-
if (archetype === "CRYSTAL") {
|
|
1704
|
-
dnaShift += _IKFirewallCore.CONSTANTS.DNA.HARDENING_MODERATE;
|
|
1705
|
-
} else if (archetype === "FLUID") {
|
|
1706
|
-
dnaShift -= _IKFirewallCore.CONSTANTS.DNA.SOFTENING_STEP;
|
|
1707
|
-
} else if (archetype === "NEURAL") {
|
|
1708
|
-
dnaShift -= _IKFirewallCore.CONSTANTS.DNA.SOFTENING_STEP / 2;
|
|
1709
|
-
}
|
|
1710
|
-
if (["LEGAL", "MEDICAL", "FINANCE", "CONSTRUCTION", "ENGINEERING"].includes(domain)) {
|
|
1711
|
-
dnaShift += _IKFirewallCore.CONSTANTS.DNA.HARDENING_DOMAIN;
|
|
1712
|
-
}
|
|
1713
|
-
if (isSystemPrompt) {
|
|
1714
|
-
dnaShift += _IKFirewallCore.CONSTANTS.DNA.HARDENING_MODERATE;
|
|
1715
|
-
}
|
|
1716
|
-
this.setSessionDNA(this.sessionDNA + dnaShift);
|
|
1717
|
-
}
|
|
1718
1996
|
async analyzeAIAssisted(input, provider, personaName = "professional", instanceId, configOverride) {
|
|
1719
1997
|
if (instanceId) {
|
|
1720
1998
|
this.configManager.ensureInstanceConfig(instanceId);
|
|
1721
1999
|
}
|
|
1722
2000
|
const activeConfig = this.configManager.getMergedConfig(instanceId, configOverride);
|
|
1723
|
-
const locale = activeConfig
|
|
2001
|
+
const locale = activeConfig?.locale || "en";
|
|
1724
2002
|
const cqScore = this.orchestrator.calculateCQ(input);
|
|
1725
2003
|
let isAuditTask = false;
|
|
1726
|
-
if (activeConfig
|
|
1727
|
-
const boundaryResult = this.gatekeeper.evaluateBoundaryCommunication(input, activeConfig
|
|
1728
|
-
if (boundaryResult
|
|
2004
|
+
if (activeConfig?.safeMode) {
|
|
2005
|
+
const boundaryResult = this.gatekeeper.evaluateBoundaryCommunication(input, activeConfig?.customBoundaryKeywords);
|
|
2006
|
+
if (boundaryResult?.requiresHumanIntervention) {
|
|
1729
2007
|
isAuditTask = true;
|
|
1730
2008
|
}
|
|
1731
2009
|
}
|
|
@@ -1947,7 +2225,11 @@ Any action outside this scope MUST be rejected and flagged as a boundary violati
|
|
|
1947
2225
|
inputLength
|
|
1948
2226
|
};
|
|
1949
2227
|
}
|
|
1950
|
-
crystallize(input, metrics, locale = "en") {
|
|
2228
|
+
crystallize(input, metrics, locale = "en", instanceId) {
|
|
2229
|
+
const mergedConfig = this.configManager.getMergedConfig(instanceId);
|
|
2230
|
+
if (instanceId && mergedConfig?.isVerified === false) {
|
|
2231
|
+
return input;
|
|
2232
|
+
}
|
|
1951
2233
|
const archetype = metrics?.semanticInsights?.archetype || "DYNAMIC";
|
|
1952
2234
|
const tolerance = metrics?.semanticInsights?.ambiguityTolerance || 0.5;
|
|
1953
2235
|
const blueprint = metrics?.semanticInsights?.conceptualBlueprint;
|
|
@@ -1961,11 +2243,13 @@ Any action outside this scope MUST be rejected and flagged as a boundary violati
|
|
|
1961
2243
|
this.hooks?.onStatus?.(`\u{1F6E1}\uFE0F IK_CRYSTALLIZE: Precision domain detected (${domain}). Relaxing noise reduction...`);
|
|
1962
2244
|
archetypeModifier = -0.3;
|
|
1963
2245
|
}
|
|
2246
|
+
const cqScore = CognitiveEngine.sniffDNA(input);
|
|
2247
|
+
const cognitiveThreshold = CognitiveEngine.calculateThreshold(cqScore);
|
|
1964
2248
|
const threshold = Math.min(
|
|
1965
2249
|
_IKFirewallCore.CONSTANTS.AI.THRESHOLD_MAX,
|
|
1966
2250
|
Math.max(
|
|
1967
2251
|
_IKFirewallCore.CONSTANTS.AI.THRESHOLD_MIN,
|
|
1968
|
-
|
|
2252
|
+
cognitiveThreshold + archetypeModifier - tolerance * 0.1
|
|
1969
2253
|
)
|
|
1970
2254
|
);
|
|
1971
2255
|
const lengthRatio = blueprint ? blueprint.length / input.length : 1;
|