@ik-firewall/core 2.3.3 → 2.3.4
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 +158 -179
- package/dist/index.d.cts +22 -2
- package/dist/index.d.ts +22 -2
- package/dist/index.js +158 -179
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -50,17 +50,12 @@ module.exports = __toCommonJS(index_exports);
|
|
|
50
50
|
// src/Registry.ts
|
|
51
51
|
var fs = null;
|
|
52
52
|
var path = null;
|
|
53
|
-
if (typeof window === "undefined") {
|
|
54
|
-
try {
|
|
55
|
-
} catch (e) {
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
53
|
var Registry = class _Registry {
|
|
59
54
|
static instance;
|
|
60
55
|
registryPath;
|
|
61
56
|
usagePath;
|
|
57
|
+
hooks;
|
|
62
58
|
hasWarnedNoFs = false;
|
|
63
|
-
hasWarnedSaveError = false;
|
|
64
59
|
constructor() {
|
|
65
60
|
this.registryPath = "";
|
|
66
61
|
this.usagePath = "";
|
|
@@ -72,12 +67,6 @@ var Registry = class _Registry {
|
|
|
72
67
|
this.usagePath = path.join(process.cwd(), ".ik-adapter", "usage.json");
|
|
73
68
|
this.ensureDirectory();
|
|
74
69
|
} catch (e) {
|
|
75
|
-
if (!this.hasWarnedNoFs) {
|
|
76
|
-
console.warn("\n[IK_REGISTRY] \u26A0\uFE0F WARNING: Node.js environment detected but fs/path modules failed to load. Falling back to in-memory mode.");
|
|
77
|
-
console.warn("[IK_REGISTRY] \u{1F4DD} INTEGRATOR NOTICE: Custom configurations (metaprompts, instances) will be LOST upon server restart or lambda spin-down.");
|
|
78
|
-
console.warn("[IK_REGISTRY] \u{1F4A1} BEST PRACTICE: Host on a traditional VPS (Node/Docker) or migrate settings to your database if you need persistence on Serverless runtimes.\n");
|
|
79
|
-
this.hasWarnedNoFs = true;
|
|
80
|
-
}
|
|
81
70
|
}
|
|
82
71
|
}
|
|
83
72
|
}
|
|
@@ -87,23 +76,31 @@ var Registry = class _Registry {
|
|
|
87
76
|
}
|
|
88
77
|
return _Registry.instance;
|
|
89
78
|
}
|
|
79
|
+
setHooks(hooks) {
|
|
80
|
+
this.hooks = hooks;
|
|
81
|
+
}
|
|
90
82
|
ensureDirectory() {
|
|
91
83
|
if (!fs || !path || !this.registryPath) return;
|
|
92
84
|
try {
|
|
93
85
|
const dir = path.dirname(this.registryPath);
|
|
94
|
-
if (!fs.existsSync(dir)) {
|
|
95
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
96
|
-
}
|
|
86
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
97
87
|
} catch (e) {
|
|
98
88
|
}
|
|
99
89
|
}
|
|
100
90
|
/**
|
|
101
|
-
* Loads
|
|
91
|
+
* Loads instance configurations with Hook support
|
|
102
92
|
*/
|
|
103
93
|
load() {
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
94
|
+
if (this.hooks?.loadState) {
|
|
95
|
+
try {
|
|
96
|
+
const hookData = this.hooks.loadState("ik_registry");
|
|
97
|
+
if (hookData) return JSON.parse(hookData);
|
|
98
|
+
} catch (e) {
|
|
99
|
+
console.warn("[IK_REGISTRY] Registry load via hook failed, falling back...");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (fs && this.registryPath && fs.existsSync(this.registryPath)) {
|
|
103
|
+
try {
|
|
107
104
|
const rawData = fs.readFileSync(this.registryPath, "utf8");
|
|
108
105
|
try {
|
|
109
106
|
const decoded = Buffer.from(rawData, "base64").toString("utf8");
|
|
@@ -111,69 +108,45 @@ var Registry = class _Registry {
|
|
|
111
108
|
} catch (e) {
|
|
112
109
|
return JSON.parse(rawData);
|
|
113
110
|
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error("[IK_REGISTRY] Local load error:", error);
|
|
114
113
|
}
|
|
115
|
-
} catch (error) {
|
|
116
|
-
console.error("[IK_REGISTRY] Load error:", error);
|
|
117
114
|
}
|
|
118
115
|
return {};
|
|
119
116
|
}
|
|
120
117
|
save(data) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
console.warn("[IK_REGISTRY] \u{1F4DD} INTEGRATOR NOTICE: Custom configurations (metaprompts, instances) will be LOST upon server restart or lambda spin-down.");
|
|
125
|
-
console.warn("[IK_REGISTRY] \u{1F4A1} BEST PRACTICE: If hosting on Vercel/Serverless or Edge runtimes, do not rely on local JSON for dynamic config changes.");
|
|
126
|
-
console.warn("[IK_REGISTRY] \u{1F449} Consider migrating user-generated adapter settings to your database or host on a traditional VPS (Node/Docker) to ensure persistence.\n");
|
|
127
|
-
this.hasWarnedNoFs = true;
|
|
128
|
-
}
|
|
129
|
-
return;
|
|
118
|
+
const jsonStr = JSON.stringify(data, null, 2);
|
|
119
|
+
if (this.hooks?.persistState) {
|
|
120
|
+
this.hooks.persistState("ik_registry", jsonStr);
|
|
130
121
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
console.error("[IK_REGISTRY] Actual Error:", error.message, "\n");
|
|
142
|
-
this.hasWarnedSaveError = true;
|
|
122
|
+
if (fs && this.registryPath) {
|
|
123
|
+
try {
|
|
124
|
+
this.ensureDirectory();
|
|
125
|
+
const obfuscated = Buffer.from(jsonStr).toString("base64");
|
|
126
|
+
fs.writeFileSync(this.registryPath, obfuscated, "utf8");
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (!this.hasWarnedNoFs) {
|
|
129
|
+
console.warn("[IK_REGISTRY] Local write failed (likely Read-Only FS). Relying on hooks/memory.");
|
|
130
|
+
this.hasWarnedNoFs = true;
|
|
131
|
+
}
|
|
143
132
|
}
|
|
144
133
|
}
|
|
145
134
|
}
|
|
146
|
-
/**
|
|
147
|
-
* Updates a specific instance configuration.
|
|
148
|
-
*/
|
|
149
135
|
updateInstance(instanceId, config) {
|
|
150
136
|
const all = this.load();
|
|
151
|
-
all[instanceId] = {
|
|
152
|
-
...all[instanceId] || {},
|
|
153
|
-
...config,
|
|
154
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
155
|
-
};
|
|
156
|
-
this.save(all);
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Deletes a specific instance configuration.
|
|
160
|
-
*/
|
|
161
|
-
deleteInstance(instanceId) {
|
|
162
|
-
const all = this.load();
|
|
163
|
-
delete all[instanceId];
|
|
137
|
+
all[instanceId] = { ...all[instanceId] || {}, ...config, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
164
138
|
this.save(all);
|
|
165
139
|
}
|
|
166
140
|
getUsageFilePath() {
|
|
167
141
|
return this.usagePath;
|
|
168
142
|
}
|
|
169
143
|
exists(filePath) {
|
|
170
|
-
return fs && fs.existsSync(filePath);
|
|
144
|
+
return !!(fs && fs.existsSync(filePath));
|
|
171
145
|
}
|
|
172
146
|
loadUsage() {
|
|
173
147
|
if (!fs || !this.usagePath || !fs.existsSync(this.usagePath)) return [];
|
|
174
148
|
try {
|
|
175
|
-
|
|
176
|
-
return JSON.parse(raw);
|
|
149
|
+
return JSON.parse(fs.readFileSync(this.usagePath, "utf8"));
|
|
177
150
|
} catch (e) {
|
|
178
151
|
return [];
|
|
179
152
|
}
|
|
@@ -183,15 +156,12 @@ var Registry = class _Registry {
|
|
|
183
156
|
try {
|
|
184
157
|
fs.writeFileSync(this.usagePath, JSON.stringify(data, null, 2), "utf8");
|
|
185
158
|
} catch (e) {
|
|
186
|
-
console.error("[IK_REGISTRY] Failed to save usage:", e);
|
|
187
159
|
}
|
|
188
160
|
}
|
|
189
161
|
clearUsage() {
|
|
190
162
|
if (!fs || !this.usagePath) return;
|
|
191
163
|
try {
|
|
192
|
-
if (fs.existsSync(this.usagePath))
|
|
193
|
-
fs.unlinkSync(this.usagePath);
|
|
194
|
-
}
|
|
164
|
+
if (fs.existsSync(this.usagePath)) fs.unlinkSync(this.usagePath);
|
|
195
165
|
} catch (e) {
|
|
196
166
|
}
|
|
197
167
|
}
|
|
@@ -236,6 +206,9 @@ var ConfigManager = class {
|
|
|
236
206
|
this.config = initialConfig;
|
|
237
207
|
this.hooks = hooks;
|
|
238
208
|
this.registry = Registry.getInstance();
|
|
209
|
+
if (hooks) {
|
|
210
|
+
this.registry.setHooks(hooks);
|
|
211
|
+
}
|
|
239
212
|
this.loadFromRegistry();
|
|
240
213
|
}
|
|
241
214
|
getRegistry() {
|
|
@@ -243,6 +216,22 @@ var ConfigManager = class {
|
|
|
243
216
|
}
|
|
244
217
|
setHooks(hooks) {
|
|
245
218
|
this.hooks = hooks;
|
|
219
|
+
this.registry.setHooks(hooks);
|
|
220
|
+
}
|
|
221
|
+
async hydrateFromHooks() {
|
|
222
|
+
if (this.hooks?.loadState) {
|
|
223
|
+
try {
|
|
224
|
+
const state = await this.hooks.loadState("ik_registry");
|
|
225
|
+
if (state) {
|
|
226
|
+
const parsed = JSON.parse(state);
|
|
227
|
+
const registry = this.registry;
|
|
228
|
+
registry.save(parsed);
|
|
229
|
+
this.loadFromRegistry();
|
|
230
|
+
}
|
|
231
|
+
} catch (e) {
|
|
232
|
+
console.warn("[IK_CONFIG] Hydration from hooks failed:", e);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
246
235
|
}
|
|
247
236
|
loadFromRegistry() {
|
|
248
237
|
const instanceConfigs = this.registry.load();
|
|
@@ -268,19 +257,16 @@ var ConfigManager = class {
|
|
|
268
257
|
try {
|
|
269
258
|
const baseUrl = this.config.centralReportEndpoint || "https://ik-firewall.vercel.app";
|
|
270
259
|
const endpoint = `${baseUrl.replace(/\/$/, "")}/api/v1/sync`;
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
if ((!instanceConfig2?.licenseKey || instanceConfig2.licenseKey.startsWith("TRIAL-")) && registrationEmail) {
|
|
260
|
+
const registrationEmail = instanceConfig?.ownerEmail || this.config.ownerEmail;
|
|
261
|
+
if ((!instanceConfig?.licenseKey || instanceConfig.licenseKey.startsWith("TRIAL-")) && registrationEmail) {
|
|
274
262
|
const success = await this.registerInstance(instanceId, registrationEmail);
|
|
275
263
|
if (success) {
|
|
276
264
|
this.hooks?.onStatus?.("\u2728 IK_SYNC: Zero-Config registration successful. License issued.");
|
|
277
265
|
return;
|
|
278
|
-
} else {
|
|
279
|
-
this.hooks?.onStatus?.("\u26A0\uFE0F IK_SYNC: Auto-registration failed. Continuing with local trial.");
|
|
280
266
|
}
|
|
281
267
|
}
|
|
282
268
|
this.hooks?.onStatus?.(`\u{1F4E1} IK_SYNC: Heartbeat to ${endpoint}...`);
|
|
283
|
-
const response = await fetch(`${endpoint}?licenseKey=${
|
|
269
|
+
const response = await fetch(`${endpoint}?licenseKey=${instanceConfig?.licenseKey || ""}&instanceId=${instanceId}&version=2.3.0`);
|
|
284
270
|
if (response.ok) {
|
|
285
271
|
const data = await response.json();
|
|
286
272
|
if (this.config.hmacSecret) {
|
|
@@ -300,9 +286,6 @@ var ConfigManager = class {
|
|
|
300
286
|
} else {
|
|
301
287
|
throw new Error(`Server responded with ${response.status}`);
|
|
302
288
|
}
|
|
303
|
-
if (!instanceConfig2?.firstUseDate) {
|
|
304
|
-
this.setInstanceConfig(instanceId, { firstUseDate: now.toISOString() });
|
|
305
|
-
}
|
|
306
289
|
} catch (error) {
|
|
307
290
|
console.error(`[IK_CONFIG] Remote sync failed (${error.message}). Falling open (Fail-Open mode).`);
|
|
308
291
|
this.setInstanceConfig(instanceId, {
|
|
@@ -310,17 +293,6 @@ var ConfigManager = class {
|
|
|
310
293
|
lastSyncDate: now.toISOString()
|
|
311
294
|
});
|
|
312
295
|
}
|
|
313
|
-
try {
|
|
314
|
-
const registry = this.registry;
|
|
315
|
-
if (registry.exists(registry.getUsageFilePath())) {
|
|
316
|
-
const aggregatedUsage = registry.loadUsage();
|
|
317
|
-
if (aggregatedUsage.length > 0) {
|
|
318
|
-
this.hooks?.onStatus?.(`\u{1F4CA} IK_SYNC: Found ${aggregatedUsage.length} pending usage reports. Preparation for Daily Flush...`);
|
|
319
|
-
this._pendingUsage = aggregatedUsage;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
} catch (e) {
|
|
323
|
-
}
|
|
324
296
|
}
|
|
325
297
|
getConfig() {
|
|
326
298
|
return { ...this.config };
|
|
@@ -1478,9 +1450,13 @@ var Orchestrator = class {
|
|
|
1478
1450
|
|
|
1479
1451
|
// src/UsageTracker.ts
|
|
1480
1452
|
var UsageTracker = class {
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1453
|
+
aggregationMap = /* @__PURE__ */ new Map();
|
|
1454
|
+
retryQueue = [];
|
|
1455
|
+
lastFlushTime = Date.now();
|
|
1456
|
+
flushInProgress = false;
|
|
1457
|
+
BATCH_SIZE_THRESHOLD = 50;
|
|
1458
|
+
TIME_THRESHOLD_MS = 2e3;
|
|
1459
|
+
MAX_RETRIES = 5;
|
|
1484
1460
|
hooks;
|
|
1485
1461
|
config;
|
|
1486
1462
|
constructor(config, hooks) {
|
|
@@ -1488,113 +1464,116 @@ var UsageTracker = class {
|
|
|
1488
1464
|
this.hooks = hooks;
|
|
1489
1465
|
}
|
|
1490
1466
|
/**
|
|
1491
|
-
* Record a single AI interaction
|
|
1467
|
+
* Record a single AI interaction and aggregate in memory
|
|
1492
1468
|
*/
|
|
1493
1469
|
async logInteraction(params) {
|
|
1494
|
-
const { instanceId, model, inputTokens, outputTokens, optimizedTokens = 0
|
|
1470
|
+
const { instanceId, model, inputTokens, outputTokens, optimizedTokens = 0 } = params;
|
|
1495
1471
|
const tokensSaved = optimizedTokens > 0 ? inputTokens - optimizedTokens : 0;
|
|
1496
|
-
const
|
|
1497
|
-
const
|
|
1498
|
-
const
|
|
1499
|
-
const
|
|
1500
|
-
|
|
1472
|
+
const totalTokens = inputTokens + outputTokens;
|
|
1473
|
+
const day = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1474
|
+
const aggKey = `${instanceId}:${model}:${day}`;
|
|
1475
|
+
const existing = this.aggregationMap.get(aggKey) || {
|
|
1476
|
+
modelName: model,
|
|
1477
|
+
date: day,
|
|
1478
|
+
tokensIn: 0,
|
|
1479
|
+
tokensOut: 0,
|
|
1480
|
+
tokensSaved: 0
|
|
1481
|
+
};
|
|
1482
|
+
existing.tokensIn += inputTokens;
|
|
1483
|
+
existing.tokensOut += outputTokens;
|
|
1484
|
+
existing.tokensSaved += tokensSaved;
|
|
1485
|
+
this.aggregationMap.set(aggKey, existing);
|
|
1486
|
+
this.hooks?.onUsage?.({
|
|
1501
1487
|
instanceId,
|
|
1502
1488
|
model_used: model,
|
|
1503
|
-
routingPath,
|
|
1489
|
+
routingPath: params.routingPath,
|
|
1504
1490
|
input_tokens: inputTokens,
|
|
1505
1491
|
output_tokens: outputTokens,
|
|
1506
1492
|
optimized_tokens: optimizedTokens,
|
|
1507
1493
|
tokens_saved: tokensSaved,
|
|
1508
|
-
cost_saved:
|
|
1509
|
-
commission_due:
|
|
1494
|
+
cost_saved: 0,
|
|
1495
|
+
commission_due: 0,
|
|
1510
1496
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1511
1497
|
is_local: model === "local",
|
|
1512
|
-
cq_score: cqScore,
|
|
1513
|
-
clientOrigin,
|
|
1514
|
-
trace
|
|
1515
|
-
};
|
|
1516
|
-
this.
|
|
1517
|
-
this.
|
|
1518
|
-
|
|
1498
|
+
cq_score: params.cqScore,
|
|
1499
|
+
clientOrigin: params.clientOrigin,
|
|
1500
|
+
trace: params.trace
|
|
1501
|
+
});
|
|
1502
|
+
this.hooks?.onUsageAggregate?.(existing);
|
|
1503
|
+
await this.checkFlushConditions(instanceId);
|
|
1504
|
+
}
|
|
1505
|
+
async checkFlushConditions(instanceId) {
|
|
1506
|
+
const now = Date.now();
|
|
1507
|
+
const timeSinceFlush = now - this.lastFlushTime;
|
|
1508
|
+
if (this.aggregationMap.size >= this.BATCH_SIZE_THRESHOLD || timeSinceFlush >= this.TIME_THRESHOLD_MS) {
|
|
1509
|
+
this.performAsyncFlush(instanceId).catch((err) => {
|
|
1510
|
+
console.error("[IK_TRACKER] Background flush failed:", err);
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1519
1513
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1514
|
+
async performAsyncFlush(instanceId) {
|
|
1515
|
+
if (this.flushInProgress || this.aggregationMap.size === 0) return;
|
|
1516
|
+
this.flushInProgress = true;
|
|
1517
|
+
const reports = Array.from(this.aggregationMap.values());
|
|
1518
|
+
this.aggregationMap.clear();
|
|
1519
|
+
this.lastFlushTime = Date.now();
|
|
1520
|
+
const licenseKey = this.config.getConfig().instanceConfigs?.[instanceId]?.licenseKey || this.config.get("licenseKey");
|
|
1521
|
+
const payload = {
|
|
1522
|
+
licenseKey: licenseKey || "TRIAL-KEY",
|
|
1523
|
+
instanceId,
|
|
1524
|
+
timestamp: Date.now(),
|
|
1525
|
+
reports
|
|
1526
|
+
};
|
|
1524
1527
|
try {
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
existingUsage.push(data);
|
|
1532
|
-
registry.saveUsage(existingUsage);
|
|
1533
|
-
} catch (e) {
|
|
1534
|
-
console.error("[IK_TRACKER] Failed to persist usage to local buffer:", e);
|
|
1528
|
+
await this.sendBatch(payload);
|
|
1529
|
+
} catch (error) {
|
|
1530
|
+
console.error("[IK_TRACKER] Batch send failed, queuing for retry:", error);
|
|
1531
|
+
this.handleFailure(payload);
|
|
1532
|
+
} finally {
|
|
1533
|
+
this.flushInProgress = false;
|
|
1535
1534
|
}
|
|
1536
1535
|
}
|
|
1537
|
-
|
|
1538
|
-
* Send buffered usage data to the central IK billing service
|
|
1539
|
-
*/
|
|
1540
|
-
async flush(aggregatedData) {
|
|
1541
|
-
const usageToFlush = aggregatedData || this.buffer;
|
|
1542
|
-
if (usageToFlush.length === 0) return;
|
|
1536
|
+
async sendBatch(payload) {
|
|
1543
1537
|
const baseUrl = this.config.get("centralReportEndpoint") || "https://ik-firewall.vercel.app";
|
|
1544
1538
|
const endpoint = `${baseUrl.replace(/\/$/, "")}/api/v1/report`;
|
|
1545
|
-
const hmacSecret = this.config.get("hmacSecret");
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
reportsByInstance[report.instanceId].push(report);
|
|
1552
|
-
}
|
|
1553
|
-
for (const [instanceId, reports] of Object.entries(reportsByInstance)) {
|
|
1554
|
-
const instanceConfig = this.config.getConfig().instanceConfigs?.[instanceId];
|
|
1555
|
-
const payload = {
|
|
1556
|
-
licenseKey: instanceConfig?.licenseKey || "",
|
|
1557
|
-
instanceId,
|
|
1558
|
-
reports: reports.map((r) => ({
|
|
1559
|
-
modelName: r.model_used,
|
|
1560
|
-
tokensIn: r.input_tokens,
|
|
1561
|
-
tokensOut: r.output_tokens,
|
|
1562
|
-
tokensSaved: r.tokens_saved,
|
|
1563
|
-
date: r.timestamp
|
|
1564
|
-
}))
|
|
1565
|
-
};
|
|
1566
|
-
if (hmacSecret) {
|
|
1567
|
-
const jsonStr = JSON.stringify(payload);
|
|
1568
|
-
try {
|
|
1569
|
-
const crypto = typeof window === "undefined" ? (await import(
|
|
1570
|
-
/* webpackIgnore: true */
|
|
1571
|
-
"crypto"
|
|
1572
|
-
)).default : null;
|
|
1573
|
-
if (crypto && crypto.createHmac) {
|
|
1574
|
-
payload.signature = crypto.createHmac("sha256", hmacSecret).update(jsonStr).digest("hex");
|
|
1575
|
-
}
|
|
1576
|
-
} catch (e) {
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
const response = await fetch(endpoint, {
|
|
1580
|
-
method: "POST",
|
|
1581
|
-
headers: { "Content-Type": "application/json" },
|
|
1582
|
-
body: JSON.stringify(payload)
|
|
1583
|
-
});
|
|
1584
|
-
if (!response.ok) {
|
|
1585
|
-
console.error(`[IK_TRACKER] Failed to send aggregate for ${instanceId}: ${response.statusText}`);
|
|
1539
|
+
const hmacSecret = this.config.get("hmacSecret") || process.env.IK_FIREWALL_SECRET;
|
|
1540
|
+
if (hmacSecret) {
|
|
1541
|
+
try {
|
|
1542
|
+
const crypto = typeof window === "undefined" ? (await import("crypto")).default : null;
|
|
1543
|
+
if (crypto && crypto.createHmac) {
|
|
1544
|
+
payload.signature = crypto.createHmac("sha256", hmacSecret).update(JSON.stringify(payload)).digest("hex");
|
|
1586
1545
|
}
|
|
1546
|
+
} catch (e) {
|
|
1587
1547
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1548
|
+
}
|
|
1549
|
+
const response = await fetch(endpoint, {
|
|
1550
|
+
method: "POST",
|
|
1551
|
+
headers: {
|
|
1552
|
+
"Content-Type": "application/json",
|
|
1553
|
+
"x-ik-signature": payload.signature || ""
|
|
1554
|
+
},
|
|
1555
|
+
body: JSON.stringify(payload)
|
|
1556
|
+
});
|
|
1557
|
+
if (!response.ok) {
|
|
1558
|
+
throw new Error(`HTTP Error: ${response.status} ${response.statusText}`);
|
|
1559
|
+
}
|
|
1560
|
+
this.hooks?.onFlushSuccess?.(payload);
|
|
1561
|
+
}
|
|
1562
|
+
async handleFailure(payload) {
|
|
1563
|
+
this.retryQueue.push(payload);
|
|
1564
|
+
if (this.config.getRegistry() && typeof window === "undefined") {
|
|
1565
|
+
try {
|
|
1566
|
+
const registry = this.config.getRegistry();
|
|
1567
|
+
const retryFile = registry.getUsageFilePath().replace("usage.json", "usage.retry.jsonl");
|
|
1568
|
+
} catch (e) {
|
|
1591
1569
|
}
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1570
|
+
}
|
|
1571
|
+
if (this.hooks?.persistState) {
|
|
1572
|
+
await this.hooks.persistState(`ik_retry_${payload.timestamp}`, JSON.stringify(payload));
|
|
1594
1573
|
}
|
|
1595
1574
|
}
|
|
1596
1575
|
getBuffer() {
|
|
1597
|
-
return
|
|
1576
|
+
return Array.from(this.aggregationMap.values());
|
|
1598
1577
|
}
|
|
1599
1578
|
};
|
|
1600
1579
|
|
|
@@ -1869,20 +1848,20 @@ var IKFirewallCore = class _IKFirewallCore {
|
|
|
1869
1848
|
async logConsumption(params) {
|
|
1870
1849
|
return this.usageTracker.logInteraction(params);
|
|
1871
1850
|
}
|
|
1851
|
+
hydrated = false;
|
|
1872
1852
|
/**
|
|
1873
1853
|
* Primary Analysis Entry Point
|
|
1874
1854
|
*/
|
|
1875
1855
|
async analyze(input, provider, personaName = "professional", locale, instanceId) {
|
|
1856
|
+
if (!this.hydrated) {
|
|
1857
|
+
await this.configManager.hydrateFromHooks();
|
|
1858
|
+
this.hydrated = true;
|
|
1859
|
+
}
|
|
1876
1860
|
if (instanceId) {
|
|
1877
1861
|
this.configManager.ensureInstanceConfig(instanceId);
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
if (pendingUsage && pendingUsage.length > 0) {
|
|
1881
|
-
await this.usageTracker.flush(pendingUsage);
|
|
1882
|
-
this.configManager._pendingUsage = null;
|
|
1883
|
-
}
|
|
1862
|
+
this.configManager.syncRemoteConfig(instanceId).catch((err) => {
|
|
1863
|
+
console.warn("[IK_CORE] Heartbeat sync failed:", err);
|
|
1884
1864
|
});
|
|
1885
|
-
await syncPromise;
|
|
1886
1865
|
}
|
|
1887
1866
|
const mergedConfig = this.configManager.getMergedConfig(instanceId);
|
|
1888
1867
|
if (instanceId && mergedConfig?.isVerified === false) {
|
package/dist/index.d.cts
CHANGED
|
@@ -165,12 +165,31 @@ interface UsageData {
|
|
|
165
165
|
clientOrigin?: string;
|
|
166
166
|
trace?: any;
|
|
167
167
|
}
|
|
168
|
+
interface UsageAggregate {
|
|
169
|
+
modelName: string;
|
|
170
|
+
date: string;
|
|
171
|
+
tokensIn: number;
|
|
172
|
+
tokensOut: number;
|
|
173
|
+
tokensSaved: number;
|
|
174
|
+
}
|
|
175
|
+
interface BatchPayload {
|
|
176
|
+
licenseKey: string;
|
|
177
|
+
instanceId: string;
|
|
178
|
+
timestamp: number;
|
|
179
|
+
reports: UsageAggregate[];
|
|
180
|
+
signature?: string;
|
|
181
|
+
}
|
|
168
182
|
interface IKHooks {
|
|
169
183
|
onDNAChange?: (dna: number) => void;
|
|
170
184
|
onAuditComplete?: (metrics: IKMetrics) => void;
|
|
171
185
|
onStatus?: (message: string) => void;
|
|
172
|
-
|
|
186
|
+
onLicenseIssued?: (license: string) => void;
|
|
187
|
+
onUsage?: (usage: UsageData) => void;
|
|
188
|
+
onUsageAggregate?: (aggregate: UsageAggregate) => void;
|
|
189
|
+
onFlushSuccess?: (payload: BatchPayload) => void;
|
|
173
190
|
onAuthorityExceeded?: (metrics: IKMetrics, prompt: string) => void | Promise<void>;
|
|
191
|
+
persistState?: (key: string, value: string) => Promise<void> | void;
|
|
192
|
+
loadState?: (key: string) => Promise<string | null> | string | null;
|
|
174
193
|
}
|
|
175
194
|
|
|
176
195
|
declare abstract class BaseProvider {
|
|
@@ -356,6 +375,7 @@ declare class IKFirewallCore {
|
|
|
356
375
|
clientOrigin?: string;
|
|
357
376
|
trace?: any;
|
|
358
377
|
}): Promise<void>;
|
|
378
|
+
private hydrated;
|
|
359
379
|
/**
|
|
360
380
|
* Primary Analysis Entry Point
|
|
361
381
|
*/
|
|
@@ -705,4 +725,4 @@ declare const IKBenchmarks: {
|
|
|
705
725
|
BIG_TEST: TestPrompt[];
|
|
706
726
|
};
|
|
707
727
|
|
|
708
|
-
export { type AIProvider, type AIResponse, AnthropicProvider, BaseProvider, type ContextMode, DeepSeekProvider, Dictionaries, DictionaryRegex, GeminiProvider, HeuristicGatekeeper, IKBenchmarks, type IKConfig, IKFirewallCore, type IKHooks, type IKMetrics, type IKPluginInfo, LocalProvider, type ModelRole, OpenAIProvider, PerplexityProvider, type PromptLength, type PromptType, type ProviderMode, type RuntimeStrategy, type StressReport, type StressTestCase, StressTester, SurgicalTester, type TestCase, type TestPrompt, type TestResult, type UsageData };
|
|
728
|
+
export { type AIProvider, type AIResponse, AnthropicProvider, BaseProvider, type BatchPayload, type ContextMode, DeepSeekProvider, Dictionaries, DictionaryRegex, GeminiProvider, HeuristicGatekeeper, IKBenchmarks, type IKConfig, IKFirewallCore, type IKHooks, type IKMetrics, type IKPluginInfo, LocalProvider, type ModelRole, OpenAIProvider, PerplexityProvider, type PromptLength, type PromptType, type ProviderMode, type RuntimeStrategy, type StressReport, type StressTestCase, StressTester, SurgicalTester, type TestCase, type TestPrompt, type TestResult, type UsageAggregate, type UsageData };
|
package/dist/index.d.ts
CHANGED
|
@@ -165,12 +165,31 @@ interface UsageData {
|
|
|
165
165
|
clientOrigin?: string;
|
|
166
166
|
trace?: any;
|
|
167
167
|
}
|
|
168
|
+
interface UsageAggregate {
|
|
169
|
+
modelName: string;
|
|
170
|
+
date: string;
|
|
171
|
+
tokensIn: number;
|
|
172
|
+
tokensOut: number;
|
|
173
|
+
tokensSaved: number;
|
|
174
|
+
}
|
|
175
|
+
interface BatchPayload {
|
|
176
|
+
licenseKey: string;
|
|
177
|
+
instanceId: string;
|
|
178
|
+
timestamp: number;
|
|
179
|
+
reports: UsageAggregate[];
|
|
180
|
+
signature?: string;
|
|
181
|
+
}
|
|
168
182
|
interface IKHooks {
|
|
169
183
|
onDNAChange?: (dna: number) => void;
|
|
170
184
|
onAuditComplete?: (metrics: IKMetrics) => void;
|
|
171
185
|
onStatus?: (message: string) => void;
|
|
172
|
-
|
|
186
|
+
onLicenseIssued?: (license: string) => void;
|
|
187
|
+
onUsage?: (usage: UsageData) => void;
|
|
188
|
+
onUsageAggregate?: (aggregate: UsageAggregate) => void;
|
|
189
|
+
onFlushSuccess?: (payload: BatchPayload) => void;
|
|
173
190
|
onAuthorityExceeded?: (metrics: IKMetrics, prompt: string) => void | Promise<void>;
|
|
191
|
+
persistState?: (key: string, value: string) => Promise<void> | void;
|
|
192
|
+
loadState?: (key: string) => Promise<string | null> | string | null;
|
|
174
193
|
}
|
|
175
194
|
|
|
176
195
|
declare abstract class BaseProvider {
|
|
@@ -356,6 +375,7 @@ declare class IKFirewallCore {
|
|
|
356
375
|
clientOrigin?: string;
|
|
357
376
|
trace?: any;
|
|
358
377
|
}): Promise<void>;
|
|
378
|
+
private hydrated;
|
|
359
379
|
/**
|
|
360
380
|
* Primary Analysis Entry Point
|
|
361
381
|
*/
|
|
@@ -705,4 +725,4 @@ declare const IKBenchmarks: {
|
|
|
705
725
|
BIG_TEST: TestPrompt[];
|
|
706
726
|
};
|
|
707
727
|
|
|
708
|
-
export { type AIProvider, type AIResponse, AnthropicProvider, BaseProvider, type ContextMode, DeepSeekProvider, Dictionaries, DictionaryRegex, GeminiProvider, HeuristicGatekeeper, IKBenchmarks, type IKConfig, IKFirewallCore, type IKHooks, type IKMetrics, type IKPluginInfo, LocalProvider, type ModelRole, OpenAIProvider, PerplexityProvider, type PromptLength, type PromptType, type ProviderMode, type RuntimeStrategy, type StressReport, type StressTestCase, StressTester, SurgicalTester, type TestCase, type TestPrompt, type TestResult, type UsageData };
|
|
728
|
+
export { type AIProvider, type AIResponse, AnthropicProvider, BaseProvider, type BatchPayload, type ContextMode, DeepSeekProvider, Dictionaries, DictionaryRegex, GeminiProvider, HeuristicGatekeeper, IKBenchmarks, type IKConfig, IKFirewallCore, type IKHooks, type IKMetrics, type IKPluginInfo, LocalProvider, type ModelRole, OpenAIProvider, PerplexityProvider, type PromptLength, type PromptType, type ProviderMode, type RuntimeStrategy, type StressReport, type StressTestCase, StressTester, SurgicalTester, type TestCase, type TestPrompt, type TestResult, type UsageAggregate, type UsageData };
|
package/dist/index.js
CHANGED
|
@@ -8,17 +8,12 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
8
8
|
// src/Registry.ts
|
|
9
9
|
var fs = null;
|
|
10
10
|
var path = null;
|
|
11
|
-
if (typeof window === "undefined") {
|
|
12
|
-
try {
|
|
13
|
-
} catch (e) {
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
11
|
var Registry = class _Registry {
|
|
17
12
|
static instance;
|
|
18
13
|
registryPath;
|
|
19
14
|
usagePath;
|
|
15
|
+
hooks;
|
|
20
16
|
hasWarnedNoFs = false;
|
|
21
|
-
hasWarnedSaveError = false;
|
|
22
17
|
constructor() {
|
|
23
18
|
this.registryPath = "";
|
|
24
19
|
this.usagePath = "";
|
|
@@ -30,12 +25,6 @@ var Registry = class _Registry {
|
|
|
30
25
|
this.usagePath = path.join(process.cwd(), ".ik-adapter", "usage.json");
|
|
31
26
|
this.ensureDirectory();
|
|
32
27
|
} catch (e) {
|
|
33
|
-
if (!this.hasWarnedNoFs) {
|
|
34
|
-
console.warn("\n[IK_REGISTRY] \u26A0\uFE0F WARNING: Node.js environment detected but fs/path modules failed to load. Falling back to in-memory mode.");
|
|
35
|
-
console.warn("[IK_REGISTRY] \u{1F4DD} INTEGRATOR NOTICE: Custom configurations (metaprompts, instances) will be LOST upon server restart or lambda spin-down.");
|
|
36
|
-
console.warn("[IK_REGISTRY] \u{1F4A1} BEST PRACTICE: Host on a traditional VPS (Node/Docker) or migrate settings to your database if you need persistence on Serverless runtimes.\n");
|
|
37
|
-
this.hasWarnedNoFs = true;
|
|
38
|
-
}
|
|
39
28
|
}
|
|
40
29
|
}
|
|
41
30
|
}
|
|
@@ -45,23 +34,31 @@ var Registry = class _Registry {
|
|
|
45
34
|
}
|
|
46
35
|
return _Registry.instance;
|
|
47
36
|
}
|
|
37
|
+
setHooks(hooks) {
|
|
38
|
+
this.hooks = hooks;
|
|
39
|
+
}
|
|
48
40
|
ensureDirectory() {
|
|
49
41
|
if (!fs || !path || !this.registryPath) return;
|
|
50
42
|
try {
|
|
51
43
|
const dir = path.dirname(this.registryPath);
|
|
52
|
-
if (!fs.existsSync(dir)) {
|
|
53
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
54
|
-
}
|
|
44
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
55
45
|
} catch (e) {
|
|
56
46
|
}
|
|
57
47
|
}
|
|
58
48
|
/**
|
|
59
|
-
* Loads
|
|
49
|
+
* Loads instance configurations with Hook support
|
|
60
50
|
*/
|
|
61
51
|
load() {
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
|
|
52
|
+
if (this.hooks?.loadState) {
|
|
53
|
+
try {
|
|
54
|
+
const hookData = this.hooks.loadState("ik_registry");
|
|
55
|
+
if (hookData) return JSON.parse(hookData);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.warn("[IK_REGISTRY] Registry load via hook failed, falling back...");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (fs && this.registryPath && fs.existsSync(this.registryPath)) {
|
|
61
|
+
try {
|
|
65
62
|
const rawData = fs.readFileSync(this.registryPath, "utf8");
|
|
66
63
|
try {
|
|
67
64
|
const decoded = Buffer.from(rawData, "base64").toString("utf8");
|
|
@@ -69,69 +66,45 @@ var Registry = class _Registry {
|
|
|
69
66
|
} catch (e) {
|
|
70
67
|
return JSON.parse(rawData);
|
|
71
68
|
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error("[IK_REGISTRY] Local load error:", error);
|
|
72
71
|
}
|
|
73
|
-
} catch (error) {
|
|
74
|
-
console.error("[IK_REGISTRY] Load error:", error);
|
|
75
72
|
}
|
|
76
73
|
return {};
|
|
77
74
|
}
|
|
78
75
|
save(data) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
console.warn("[IK_REGISTRY] \u{1F4DD} INTEGRATOR NOTICE: Custom configurations (metaprompts, instances) will be LOST upon server restart or lambda spin-down.");
|
|
83
|
-
console.warn("[IK_REGISTRY] \u{1F4A1} BEST PRACTICE: If hosting on Vercel/Serverless or Edge runtimes, do not rely on local JSON for dynamic config changes.");
|
|
84
|
-
console.warn("[IK_REGISTRY] \u{1F449} Consider migrating user-generated adapter settings to your database or host on a traditional VPS (Node/Docker) to ensure persistence.\n");
|
|
85
|
-
this.hasWarnedNoFs = true;
|
|
86
|
-
}
|
|
87
|
-
return;
|
|
76
|
+
const jsonStr = JSON.stringify(data, null, 2);
|
|
77
|
+
if (this.hooks?.persistState) {
|
|
78
|
+
this.hooks.persistState("ik_registry", jsonStr);
|
|
88
79
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
console.error("[IK_REGISTRY] Actual Error:", error.message, "\n");
|
|
100
|
-
this.hasWarnedSaveError = true;
|
|
80
|
+
if (fs && this.registryPath) {
|
|
81
|
+
try {
|
|
82
|
+
this.ensureDirectory();
|
|
83
|
+
const obfuscated = Buffer.from(jsonStr).toString("base64");
|
|
84
|
+
fs.writeFileSync(this.registryPath, obfuscated, "utf8");
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (!this.hasWarnedNoFs) {
|
|
87
|
+
console.warn("[IK_REGISTRY] Local write failed (likely Read-Only FS). Relying on hooks/memory.");
|
|
88
|
+
this.hasWarnedNoFs = true;
|
|
89
|
+
}
|
|
101
90
|
}
|
|
102
91
|
}
|
|
103
92
|
}
|
|
104
|
-
/**
|
|
105
|
-
* Updates a specific instance configuration.
|
|
106
|
-
*/
|
|
107
93
|
updateInstance(instanceId, config) {
|
|
108
94
|
const all = this.load();
|
|
109
|
-
all[instanceId] = {
|
|
110
|
-
...all[instanceId] || {},
|
|
111
|
-
...config,
|
|
112
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
113
|
-
};
|
|
114
|
-
this.save(all);
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Deletes a specific instance configuration.
|
|
118
|
-
*/
|
|
119
|
-
deleteInstance(instanceId) {
|
|
120
|
-
const all = this.load();
|
|
121
|
-
delete all[instanceId];
|
|
95
|
+
all[instanceId] = { ...all[instanceId] || {}, ...config, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
122
96
|
this.save(all);
|
|
123
97
|
}
|
|
124
98
|
getUsageFilePath() {
|
|
125
99
|
return this.usagePath;
|
|
126
100
|
}
|
|
127
101
|
exists(filePath) {
|
|
128
|
-
return fs && fs.existsSync(filePath);
|
|
102
|
+
return !!(fs && fs.existsSync(filePath));
|
|
129
103
|
}
|
|
130
104
|
loadUsage() {
|
|
131
105
|
if (!fs || !this.usagePath || !fs.existsSync(this.usagePath)) return [];
|
|
132
106
|
try {
|
|
133
|
-
|
|
134
|
-
return JSON.parse(raw);
|
|
107
|
+
return JSON.parse(fs.readFileSync(this.usagePath, "utf8"));
|
|
135
108
|
} catch (e) {
|
|
136
109
|
return [];
|
|
137
110
|
}
|
|
@@ -141,15 +114,12 @@ var Registry = class _Registry {
|
|
|
141
114
|
try {
|
|
142
115
|
fs.writeFileSync(this.usagePath, JSON.stringify(data, null, 2), "utf8");
|
|
143
116
|
} catch (e) {
|
|
144
|
-
console.error("[IK_REGISTRY] Failed to save usage:", e);
|
|
145
117
|
}
|
|
146
118
|
}
|
|
147
119
|
clearUsage() {
|
|
148
120
|
if (!fs || !this.usagePath) return;
|
|
149
121
|
try {
|
|
150
|
-
if (fs.existsSync(this.usagePath))
|
|
151
|
-
fs.unlinkSync(this.usagePath);
|
|
152
|
-
}
|
|
122
|
+
if (fs.existsSync(this.usagePath)) fs.unlinkSync(this.usagePath);
|
|
153
123
|
} catch (e) {
|
|
154
124
|
}
|
|
155
125
|
}
|
|
@@ -194,6 +164,9 @@ var ConfigManager = class {
|
|
|
194
164
|
this.config = initialConfig;
|
|
195
165
|
this.hooks = hooks;
|
|
196
166
|
this.registry = Registry.getInstance();
|
|
167
|
+
if (hooks) {
|
|
168
|
+
this.registry.setHooks(hooks);
|
|
169
|
+
}
|
|
197
170
|
this.loadFromRegistry();
|
|
198
171
|
}
|
|
199
172
|
getRegistry() {
|
|
@@ -201,6 +174,22 @@ var ConfigManager = class {
|
|
|
201
174
|
}
|
|
202
175
|
setHooks(hooks) {
|
|
203
176
|
this.hooks = hooks;
|
|
177
|
+
this.registry.setHooks(hooks);
|
|
178
|
+
}
|
|
179
|
+
async hydrateFromHooks() {
|
|
180
|
+
if (this.hooks?.loadState) {
|
|
181
|
+
try {
|
|
182
|
+
const state = await this.hooks.loadState("ik_registry");
|
|
183
|
+
if (state) {
|
|
184
|
+
const parsed = JSON.parse(state);
|
|
185
|
+
const registry = this.registry;
|
|
186
|
+
registry.save(parsed);
|
|
187
|
+
this.loadFromRegistry();
|
|
188
|
+
}
|
|
189
|
+
} catch (e) {
|
|
190
|
+
console.warn("[IK_CONFIG] Hydration from hooks failed:", e);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
204
193
|
}
|
|
205
194
|
loadFromRegistry() {
|
|
206
195
|
const instanceConfigs = this.registry.load();
|
|
@@ -226,19 +215,16 @@ var ConfigManager = class {
|
|
|
226
215
|
try {
|
|
227
216
|
const baseUrl = this.config.centralReportEndpoint || "https://ik-firewall.vercel.app";
|
|
228
217
|
const endpoint = `${baseUrl.replace(/\/$/, "")}/api/v1/sync`;
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
if ((!instanceConfig2?.licenseKey || instanceConfig2.licenseKey.startsWith("TRIAL-")) && registrationEmail) {
|
|
218
|
+
const registrationEmail = instanceConfig?.ownerEmail || this.config.ownerEmail;
|
|
219
|
+
if ((!instanceConfig?.licenseKey || instanceConfig.licenseKey.startsWith("TRIAL-")) && registrationEmail) {
|
|
232
220
|
const success = await this.registerInstance(instanceId, registrationEmail);
|
|
233
221
|
if (success) {
|
|
234
222
|
this.hooks?.onStatus?.("\u2728 IK_SYNC: Zero-Config registration successful. License issued.");
|
|
235
223
|
return;
|
|
236
|
-
} else {
|
|
237
|
-
this.hooks?.onStatus?.("\u26A0\uFE0F IK_SYNC: Auto-registration failed. Continuing with local trial.");
|
|
238
224
|
}
|
|
239
225
|
}
|
|
240
226
|
this.hooks?.onStatus?.(`\u{1F4E1} IK_SYNC: Heartbeat to ${endpoint}...`);
|
|
241
|
-
const response = await fetch(`${endpoint}?licenseKey=${
|
|
227
|
+
const response = await fetch(`${endpoint}?licenseKey=${instanceConfig?.licenseKey || ""}&instanceId=${instanceId}&version=2.3.0`);
|
|
242
228
|
if (response.ok) {
|
|
243
229
|
const data = await response.json();
|
|
244
230
|
if (this.config.hmacSecret) {
|
|
@@ -258,9 +244,6 @@ var ConfigManager = class {
|
|
|
258
244
|
} else {
|
|
259
245
|
throw new Error(`Server responded with ${response.status}`);
|
|
260
246
|
}
|
|
261
|
-
if (!instanceConfig2?.firstUseDate) {
|
|
262
|
-
this.setInstanceConfig(instanceId, { firstUseDate: now.toISOString() });
|
|
263
|
-
}
|
|
264
247
|
} catch (error) {
|
|
265
248
|
console.error(`[IK_CONFIG] Remote sync failed (${error.message}). Falling open (Fail-Open mode).`);
|
|
266
249
|
this.setInstanceConfig(instanceId, {
|
|
@@ -268,17 +251,6 @@ var ConfigManager = class {
|
|
|
268
251
|
lastSyncDate: now.toISOString()
|
|
269
252
|
});
|
|
270
253
|
}
|
|
271
|
-
try {
|
|
272
|
-
const registry = this.registry;
|
|
273
|
-
if (registry.exists(registry.getUsageFilePath())) {
|
|
274
|
-
const aggregatedUsage = registry.loadUsage();
|
|
275
|
-
if (aggregatedUsage.length > 0) {
|
|
276
|
-
this.hooks?.onStatus?.(`\u{1F4CA} IK_SYNC: Found ${aggregatedUsage.length} pending usage reports. Preparation for Daily Flush...`);
|
|
277
|
-
this._pendingUsage = aggregatedUsage;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
} catch (e) {
|
|
281
|
-
}
|
|
282
254
|
}
|
|
283
255
|
getConfig() {
|
|
284
256
|
return { ...this.config };
|
|
@@ -1436,9 +1408,13 @@ var Orchestrator = class {
|
|
|
1436
1408
|
|
|
1437
1409
|
// src/UsageTracker.ts
|
|
1438
1410
|
var UsageTracker = class {
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1411
|
+
aggregationMap = /* @__PURE__ */ new Map();
|
|
1412
|
+
retryQueue = [];
|
|
1413
|
+
lastFlushTime = Date.now();
|
|
1414
|
+
flushInProgress = false;
|
|
1415
|
+
BATCH_SIZE_THRESHOLD = 50;
|
|
1416
|
+
TIME_THRESHOLD_MS = 2e3;
|
|
1417
|
+
MAX_RETRIES = 5;
|
|
1442
1418
|
hooks;
|
|
1443
1419
|
config;
|
|
1444
1420
|
constructor(config, hooks) {
|
|
@@ -1446,113 +1422,116 @@ var UsageTracker = class {
|
|
|
1446
1422
|
this.hooks = hooks;
|
|
1447
1423
|
}
|
|
1448
1424
|
/**
|
|
1449
|
-
* Record a single AI interaction
|
|
1425
|
+
* Record a single AI interaction and aggregate in memory
|
|
1450
1426
|
*/
|
|
1451
1427
|
async logInteraction(params) {
|
|
1452
|
-
const { instanceId, model, inputTokens, outputTokens, optimizedTokens = 0
|
|
1428
|
+
const { instanceId, model, inputTokens, outputTokens, optimizedTokens = 0 } = params;
|
|
1453
1429
|
const tokensSaved = optimizedTokens > 0 ? inputTokens - optimizedTokens : 0;
|
|
1454
|
-
const
|
|
1455
|
-
const
|
|
1456
|
-
const
|
|
1457
|
-
const
|
|
1458
|
-
|
|
1430
|
+
const totalTokens = inputTokens + outputTokens;
|
|
1431
|
+
const day = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1432
|
+
const aggKey = `${instanceId}:${model}:${day}`;
|
|
1433
|
+
const existing = this.aggregationMap.get(aggKey) || {
|
|
1434
|
+
modelName: model,
|
|
1435
|
+
date: day,
|
|
1436
|
+
tokensIn: 0,
|
|
1437
|
+
tokensOut: 0,
|
|
1438
|
+
tokensSaved: 0
|
|
1439
|
+
};
|
|
1440
|
+
existing.tokensIn += inputTokens;
|
|
1441
|
+
existing.tokensOut += outputTokens;
|
|
1442
|
+
existing.tokensSaved += tokensSaved;
|
|
1443
|
+
this.aggregationMap.set(aggKey, existing);
|
|
1444
|
+
this.hooks?.onUsage?.({
|
|
1459
1445
|
instanceId,
|
|
1460
1446
|
model_used: model,
|
|
1461
|
-
routingPath,
|
|
1447
|
+
routingPath: params.routingPath,
|
|
1462
1448
|
input_tokens: inputTokens,
|
|
1463
1449
|
output_tokens: outputTokens,
|
|
1464
1450
|
optimized_tokens: optimizedTokens,
|
|
1465
1451
|
tokens_saved: tokensSaved,
|
|
1466
|
-
cost_saved:
|
|
1467
|
-
commission_due:
|
|
1452
|
+
cost_saved: 0,
|
|
1453
|
+
commission_due: 0,
|
|
1468
1454
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1469
1455
|
is_local: model === "local",
|
|
1470
|
-
cq_score: cqScore,
|
|
1471
|
-
clientOrigin,
|
|
1472
|
-
trace
|
|
1473
|
-
};
|
|
1474
|
-
this.
|
|
1475
|
-
this.
|
|
1476
|
-
|
|
1456
|
+
cq_score: params.cqScore,
|
|
1457
|
+
clientOrigin: params.clientOrigin,
|
|
1458
|
+
trace: params.trace
|
|
1459
|
+
});
|
|
1460
|
+
this.hooks?.onUsageAggregate?.(existing);
|
|
1461
|
+
await this.checkFlushConditions(instanceId);
|
|
1462
|
+
}
|
|
1463
|
+
async checkFlushConditions(instanceId) {
|
|
1464
|
+
const now = Date.now();
|
|
1465
|
+
const timeSinceFlush = now - this.lastFlushTime;
|
|
1466
|
+
if (this.aggregationMap.size >= this.BATCH_SIZE_THRESHOLD || timeSinceFlush >= this.TIME_THRESHOLD_MS) {
|
|
1467
|
+
this.performAsyncFlush(instanceId).catch((err) => {
|
|
1468
|
+
console.error("[IK_TRACKER] Background flush failed:", err);
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1477
1471
|
}
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1472
|
+
async performAsyncFlush(instanceId) {
|
|
1473
|
+
if (this.flushInProgress || this.aggregationMap.size === 0) return;
|
|
1474
|
+
this.flushInProgress = true;
|
|
1475
|
+
const reports = Array.from(this.aggregationMap.values());
|
|
1476
|
+
this.aggregationMap.clear();
|
|
1477
|
+
this.lastFlushTime = Date.now();
|
|
1478
|
+
const licenseKey = this.config.getConfig().instanceConfigs?.[instanceId]?.licenseKey || this.config.get("licenseKey");
|
|
1479
|
+
const payload = {
|
|
1480
|
+
licenseKey: licenseKey || "TRIAL-KEY",
|
|
1481
|
+
instanceId,
|
|
1482
|
+
timestamp: Date.now(),
|
|
1483
|
+
reports
|
|
1484
|
+
};
|
|
1482
1485
|
try {
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
existingUsage.push(data);
|
|
1490
|
-
registry.saveUsage(existingUsage);
|
|
1491
|
-
} catch (e) {
|
|
1492
|
-
console.error("[IK_TRACKER] Failed to persist usage to local buffer:", e);
|
|
1486
|
+
await this.sendBatch(payload);
|
|
1487
|
+
} catch (error) {
|
|
1488
|
+
console.error("[IK_TRACKER] Batch send failed, queuing for retry:", error);
|
|
1489
|
+
this.handleFailure(payload);
|
|
1490
|
+
} finally {
|
|
1491
|
+
this.flushInProgress = false;
|
|
1493
1492
|
}
|
|
1494
1493
|
}
|
|
1495
|
-
|
|
1496
|
-
* Send buffered usage data to the central IK billing service
|
|
1497
|
-
*/
|
|
1498
|
-
async flush(aggregatedData) {
|
|
1499
|
-
const usageToFlush = aggregatedData || this.buffer;
|
|
1500
|
-
if (usageToFlush.length === 0) return;
|
|
1494
|
+
async sendBatch(payload) {
|
|
1501
1495
|
const baseUrl = this.config.get("centralReportEndpoint") || "https://ik-firewall.vercel.app";
|
|
1502
1496
|
const endpoint = `${baseUrl.replace(/\/$/, "")}/api/v1/report`;
|
|
1503
|
-
const hmacSecret = this.config.get("hmacSecret");
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
reportsByInstance[report.instanceId].push(report);
|
|
1510
|
-
}
|
|
1511
|
-
for (const [instanceId, reports] of Object.entries(reportsByInstance)) {
|
|
1512
|
-
const instanceConfig = this.config.getConfig().instanceConfigs?.[instanceId];
|
|
1513
|
-
const payload = {
|
|
1514
|
-
licenseKey: instanceConfig?.licenseKey || "",
|
|
1515
|
-
instanceId,
|
|
1516
|
-
reports: reports.map((r) => ({
|
|
1517
|
-
modelName: r.model_used,
|
|
1518
|
-
tokensIn: r.input_tokens,
|
|
1519
|
-
tokensOut: r.output_tokens,
|
|
1520
|
-
tokensSaved: r.tokens_saved,
|
|
1521
|
-
date: r.timestamp
|
|
1522
|
-
}))
|
|
1523
|
-
};
|
|
1524
|
-
if (hmacSecret) {
|
|
1525
|
-
const jsonStr = JSON.stringify(payload);
|
|
1526
|
-
try {
|
|
1527
|
-
const crypto = typeof window === "undefined" ? (await import(
|
|
1528
|
-
/* webpackIgnore: true */
|
|
1529
|
-
"crypto"
|
|
1530
|
-
)).default : null;
|
|
1531
|
-
if (crypto && crypto.createHmac) {
|
|
1532
|
-
payload.signature = crypto.createHmac("sha256", hmacSecret).update(jsonStr).digest("hex");
|
|
1533
|
-
}
|
|
1534
|
-
} catch (e) {
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
const response = await fetch(endpoint, {
|
|
1538
|
-
method: "POST",
|
|
1539
|
-
headers: { "Content-Type": "application/json" },
|
|
1540
|
-
body: JSON.stringify(payload)
|
|
1541
|
-
});
|
|
1542
|
-
if (!response.ok) {
|
|
1543
|
-
console.error(`[IK_TRACKER] Failed to send aggregate for ${instanceId}: ${response.statusText}`);
|
|
1497
|
+
const hmacSecret = this.config.get("hmacSecret") || process.env.IK_FIREWALL_SECRET;
|
|
1498
|
+
if (hmacSecret) {
|
|
1499
|
+
try {
|
|
1500
|
+
const crypto = typeof window === "undefined" ? (await import("crypto")).default : null;
|
|
1501
|
+
if (crypto && crypto.createHmac) {
|
|
1502
|
+
payload.signature = crypto.createHmac("sha256", hmacSecret).update(JSON.stringify(payload)).digest("hex");
|
|
1544
1503
|
}
|
|
1504
|
+
} catch (e) {
|
|
1545
1505
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1506
|
+
}
|
|
1507
|
+
const response = await fetch(endpoint, {
|
|
1508
|
+
method: "POST",
|
|
1509
|
+
headers: {
|
|
1510
|
+
"Content-Type": "application/json",
|
|
1511
|
+
"x-ik-signature": payload.signature || ""
|
|
1512
|
+
},
|
|
1513
|
+
body: JSON.stringify(payload)
|
|
1514
|
+
});
|
|
1515
|
+
if (!response.ok) {
|
|
1516
|
+
throw new Error(`HTTP Error: ${response.status} ${response.statusText}`);
|
|
1517
|
+
}
|
|
1518
|
+
this.hooks?.onFlushSuccess?.(payload);
|
|
1519
|
+
}
|
|
1520
|
+
async handleFailure(payload) {
|
|
1521
|
+
this.retryQueue.push(payload);
|
|
1522
|
+
if (this.config.getRegistry() && typeof window === "undefined") {
|
|
1523
|
+
try {
|
|
1524
|
+
const registry = this.config.getRegistry();
|
|
1525
|
+
const retryFile = registry.getUsageFilePath().replace("usage.json", "usage.retry.jsonl");
|
|
1526
|
+
} catch (e) {
|
|
1549
1527
|
}
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1528
|
+
}
|
|
1529
|
+
if (this.hooks?.persistState) {
|
|
1530
|
+
await this.hooks.persistState(`ik_retry_${payload.timestamp}`, JSON.stringify(payload));
|
|
1552
1531
|
}
|
|
1553
1532
|
}
|
|
1554
1533
|
getBuffer() {
|
|
1555
|
-
return
|
|
1534
|
+
return Array.from(this.aggregationMap.values());
|
|
1556
1535
|
}
|
|
1557
1536
|
};
|
|
1558
1537
|
|
|
@@ -1827,20 +1806,20 @@ var IKFirewallCore = class _IKFirewallCore {
|
|
|
1827
1806
|
async logConsumption(params) {
|
|
1828
1807
|
return this.usageTracker.logInteraction(params);
|
|
1829
1808
|
}
|
|
1809
|
+
hydrated = false;
|
|
1830
1810
|
/**
|
|
1831
1811
|
* Primary Analysis Entry Point
|
|
1832
1812
|
*/
|
|
1833
1813
|
async analyze(input, provider, personaName = "professional", locale, instanceId) {
|
|
1814
|
+
if (!this.hydrated) {
|
|
1815
|
+
await this.configManager.hydrateFromHooks();
|
|
1816
|
+
this.hydrated = true;
|
|
1817
|
+
}
|
|
1834
1818
|
if (instanceId) {
|
|
1835
1819
|
this.configManager.ensureInstanceConfig(instanceId);
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
if (pendingUsage && pendingUsage.length > 0) {
|
|
1839
|
-
await this.usageTracker.flush(pendingUsage);
|
|
1840
|
-
this.configManager._pendingUsage = null;
|
|
1841
|
-
}
|
|
1820
|
+
this.configManager.syncRemoteConfig(instanceId).catch((err) => {
|
|
1821
|
+
console.warn("[IK_CORE] Heartbeat sync failed:", err);
|
|
1842
1822
|
});
|
|
1843
|
-
await syncPromise;
|
|
1844
1823
|
}
|
|
1845
1824
|
const mergedConfig = this.configManager.getMergedConfig(instanceId);
|
|
1846
1825
|
if (instanceId && mergedConfig?.isVerified === false) {
|