@ik-firewall/core 2.3.2 → 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 +165 -172
- package/dist/index.d.cts +22 -2
- package/dist/index.d.ts +22 -2
- package/dist/index.js +165 -172
- 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
|
}
|
|
@@ -211,7 +181,13 @@ var IKHmac = class {
|
|
|
211
181
|
const { signature, ...rest } = data;
|
|
212
182
|
const payload = JSON.stringify(rest);
|
|
213
183
|
try {
|
|
214
|
-
const crypto = await import(
|
|
184
|
+
const crypto = typeof window === "undefined" ? (await import(
|
|
185
|
+
/* webpackIgnore: true */
|
|
186
|
+
"crypto"
|
|
187
|
+
)).default : null;
|
|
188
|
+
if (!crypto || !crypto.createHmac) {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
215
191
|
const expectedSignature = crypto.createHmac("sha256", this.secret).update(payload).digest("hex");
|
|
216
192
|
return signature === expectedSignature;
|
|
217
193
|
} catch (e) {
|
|
@@ -230,6 +206,9 @@ var ConfigManager = class {
|
|
|
230
206
|
this.config = initialConfig;
|
|
231
207
|
this.hooks = hooks;
|
|
232
208
|
this.registry = Registry.getInstance();
|
|
209
|
+
if (hooks) {
|
|
210
|
+
this.registry.setHooks(hooks);
|
|
211
|
+
}
|
|
233
212
|
this.loadFromRegistry();
|
|
234
213
|
}
|
|
235
214
|
getRegistry() {
|
|
@@ -237,6 +216,22 @@ var ConfigManager = class {
|
|
|
237
216
|
}
|
|
238
217
|
setHooks(hooks) {
|
|
239
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
|
+
}
|
|
240
235
|
}
|
|
241
236
|
loadFromRegistry() {
|
|
242
237
|
const instanceConfigs = this.registry.load();
|
|
@@ -262,19 +257,16 @@ var ConfigManager = class {
|
|
|
262
257
|
try {
|
|
263
258
|
const baseUrl = this.config.centralReportEndpoint || "https://ik-firewall.vercel.app";
|
|
264
259
|
const endpoint = `${baseUrl.replace(/\/$/, "")}/api/v1/sync`;
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
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) {
|
|
268
262
|
const success = await this.registerInstance(instanceId, registrationEmail);
|
|
269
263
|
if (success) {
|
|
270
264
|
this.hooks?.onStatus?.("\u2728 IK_SYNC: Zero-Config registration successful. License issued.");
|
|
271
265
|
return;
|
|
272
|
-
} else {
|
|
273
|
-
this.hooks?.onStatus?.("\u26A0\uFE0F IK_SYNC: Auto-registration failed. Continuing with local trial.");
|
|
274
266
|
}
|
|
275
267
|
}
|
|
276
268
|
this.hooks?.onStatus?.(`\u{1F4E1} IK_SYNC: Heartbeat to ${endpoint}...`);
|
|
277
|
-
const response = await fetch(`${endpoint}?licenseKey=${
|
|
269
|
+
const response = await fetch(`${endpoint}?licenseKey=${instanceConfig?.licenseKey || ""}&instanceId=${instanceId}&version=2.3.0`);
|
|
278
270
|
if (response.ok) {
|
|
279
271
|
const data = await response.json();
|
|
280
272
|
if (this.config.hmacSecret) {
|
|
@@ -294,9 +286,6 @@ var ConfigManager = class {
|
|
|
294
286
|
} else {
|
|
295
287
|
throw new Error(`Server responded with ${response.status}`);
|
|
296
288
|
}
|
|
297
|
-
if (!instanceConfig2?.firstUseDate) {
|
|
298
|
-
this.setInstanceConfig(instanceId, { firstUseDate: now.toISOString() });
|
|
299
|
-
}
|
|
300
289
|
} catch (error) {
|
|
301
290
|
console.error(`[IK_CONFIG] Remote sync failed (${error.message}). Falling open (Fail-Open mode).`);
|
|
302
291
|
this.setInstanceConfig(instanceId, {
|
|
@@ -304,17 +293,6 @@ var ConfigManager = class {
|
|
|
304
293
|
lastSyncDate: now.toISOString()
|
|
305
294
|
});
|
|
306
295
|
}
|
|
307
|
-
try {
|
|
308
|
-
const registry = this.registry;
|
|
309
|
-
if (registry.exists(registry.getUsageFilePath())) {
|
|
310
|
-
const aggregatedUsage = registry.loadUsage();
|
|
311
|
-
if (aggregatedUsage.length > 0) {
|
|
312
|
-
this.hooks?.onStatus?.(`\u{1F4CA} IK_SYNC: Found ${aggregatedUsage.length} pending usage reports. Preparation for Daily Flush...`);
|
|
313
|
-
this._pendingUsage = aggregatedUsage;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
} catch (e) {
|
|
317
|
-
}
|
|
318
296
|
}
|
|
319
297
|
getConfig() {
|
|
320
298
|
return { ...this.config };
|
|
@@ -1472,9 +1450,13 @@ var Orchestrator = class {
|
|
|
1472
1450
|
|
|
1473
1451
|
// src/UsageTracker.ts
|
|
1474
1452
|
var UsageTracker = class {
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
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;
|
|
1478
1460
|
hooks;
|
|
1479
1461
|
config;
|
|
1480
1462
|
constructor(config, hooks) {
|
|
@@ -1482,105 +1464,116 @@ var UsageTracker = class {
|
|
|
1482
1464
|
this.hooks = hooks;
|
|
1483
1465
|
}
|
|
1484
1466
|
/**
|
|
1485
|
-
* Record a single AI interaction
|
|
1467
|
+
* Record a single AI interaction and aggregate in memory
|
|
1486
1468
|
*/
|
|
1487
1469
|
async logInteraction(params) {
|
|
1488
|
-
const { instanceId, model, inputTokens, outputTokens, optimizedTokens = 0
|
|
1470
|
+
const { instanceId, model, inputTokens, outputTokens, optimizedTokens = 0 } = params;
|
|
1489
1471
|
const tokensSaved = optimizedTokens > 0 ? inputTokens - optimizedTokens : 0;
|
|
1490
|
-
const
|
|
1491
|
-
const
|
|
1492
|
-
const
|
|
1493
|
-
const
|
|
1494
|
-
|
|
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?.({
|
|
1495
1487
|
instanceId,
|
|
1496
1488
|
model_used: model,
|
|
1497
|
-
routingPath,
|
|
1489
|
+
routingPath: params.routingPath,
|
|
1498
1490
|
input_tokens: inputTokens,
|
|
1499
1491
|
output_tokens: outputTokens,
|
|
1500
1492
|
optimized_tokens: optimizedTokens,
|
|
1501
1493
|
tokens_saved: tokensSaved,
|
|
1502
|
-
cost_saved:
|
|
1503
|
-
commission_due:
|
|
1494
|
+
cost_saved: 0,
|
|
1495
|
+
commission_due: 0,
|
|
1504
1496
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1505
1497
|
is_local: model === "local",
|
|
1506
|
-
cq_score: cqScore,
|
|
1507
|
-
clientOrigin,
|
|
1508
|
-
trace
|
|
1509
|
-
};
|
|
1510
|
-
this.
|
|
1511
|
-
this.
|
|
1512
|
-
|
|
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
|
+
}
|
|
1513
1513
|
}
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
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
|
+
};
|
|
1518
1527
|
try {
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
existingUsage.push(data);
|
|
1526
|
-
registry.saveUsage(existingUsage);
|
|
1527
|
-
} catch (e) {
|
|
1528
|
-
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;
|
|
1529
1534
|
}
|
|
1530
1535
|
}
|
|
1531
|
-
|
|
1532
|
-
* Send buffered usage data to the central IK billing service
|
|
1533
|
-
*/
|
|
1534
|
-
async flush(aggregatedData) {
|
|
1535
|
-
const usageToFlush = aggregatedData || this.buffer;
|
|
1536
|
-
if (usageToFlush.length === 0) return;
|
|
1536
|
+
async sendBatch(payload) {
|
|
1537
1537
|
const baseUrl = this.config.get("centralReportEndpoint") || "https://ik-firewall.vercel.app";
|
|
1538
1538
|
const endpoint = `${baseUrl.replace(/\/$/, "")}/api/v1/report`;
|
|
1539
|
-
const hmacSecret = this.config.get("hmacSecret");
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
reportsByInstance[report.instanceId].push(report);
|
|
1546
|
-
}
|
|
1547
|
-
for (const [instanceId, reports] of Object.entries(reportsByInstance)) {
|
|
1548
|
-
const instanceConfig = this.config.getConfig().instanceConfigs?.[instanceId];
|
|
1549
|
-
const payload = {
|
|
1550
|
-
licenseKey: instanceConfig?.licenseKey || "",
|
|
1551
|
-
instanceId,
|
|
1552
|
-
reports: reports.map((r) => ({
|
|
1553
|
-
modelName: r.model_used,
|
|
1554
|
-
tokensIn: r.input_tokens,
|
|
1555
|
-
tokensOut: r.output_tokens,
|
|
1556
|
-
tokensSaved: r.tokens_saved,
|
|
1557
|
-
date: r.timestamp
|
|
1558
|
-
}))
|
|
1559
|
-
};
|
|
1560
|
-
if (hmacSecret) {
|
|
1561
|
-
const jsonStr = JSON.stringify(payload);
|
|
1562
|
-
const crypto = await import("crypto");
|
|
1563
|
-
payload.signature = crypto.createHmac("sha256", hmacSecret).update(jsonStr).digest("hex");
|
|
1564
|
-
}
|
|
1565
|
-
const response = await fetch(endpoint, {
|
|
1566
|
-
method: "POST",
|
|
1567
|
-
headers: { "Content-Type": "application/json" },
|
|
1568
|
-
body: JSON.stringify(payload)
|
|
1569
|
-
});
|
|
1570
|
-
if (!response.ok) {
|
|
1571
|
-
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");
|
|
1572
1545
|
}
|
|
1546
|
+
} catch (e) {
|
|
1573
1547
|
}
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
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) {
|
|
1577
1569
|
}
|
|
1578
|
-
}
|
|
1579
|
-
|
|
1570
|
+
}
|
|
1571
|
+
if (this.hooks?.persistState) {
|
|
1572
|
+
await this.hooks.persistState(`ik_retry_${payload.timestamp}`, JSON.stringify(payload));
|
|
1580
1573
|
}
|
|
1581
1574
|
}
|
|
1582
1575
|
getBuffer() {
|
|
1583
|
-
return
|
|
1576
|
+
return Array.from(this.aggregationMap.values());
|
|
1584
1577
|
}
|
|
1585
1578
|
};
|
|
1586
1579
|
|
|
@@ -1855,20 +1848,20 @@ var IKFirewallCore = class _IKFirewallCore {
|
|
|
1855
1848
|
async logConsumption(params) {
|
|
1856
1849
|
return this.usageTracker.logInteraction(params);
|
|
1857
1850
|
}
|
|
1851
|
+
hydrated = false;
|
|
1858
1852
|
/**
|
|
1859
1853
|
* Primary Analysis Entry Point
|
|
1860
1854
|
*/
|
|
1861
1855
|
async analyze(input, provider, personaName = "professional", locale, instanceId) {
|
|
1856
|
+
if (!this.hydrated) {
|
|
1857
|
+
await this.configManager.hydrateFromHooks();
|
|
1858
|
+
this.hydrated = true;
|
|
1859
|
+
}
|
|
1862
1860
|
if (instanceId) {
|
|
1863
1861
|
this.configManager.ensureInstanceConfig(instanceId);
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
if (pendingUsage && pendingUsage.length > 0) {
|
|
1867
|
-
await this.usageTracker.flush(pendingUsage);
|
|
1868
|
-
this.configManager._pendingUsage = null;
|
|
1869
|
-
}
|
|
1862
|
+
this.configManager.syncRemoteConfig(instanceId).catch((err) => {
|
|
1863
|
+
console.warn("[IK_CORE] Heartbeat sync failed:", err);
|
|
1870
1864
|
});
|
|
1871
|
-
await syncPromise;
|
|
1872
1865
|
}
|
|
1873
1866
|
const mergedConfig = this.configManager.getMergedConfig(instanceId);
|
|
1874
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
|
}
|
|
@@ -169,7 +139,13 @@ var IKHmac = class {
|
|
|
169
139
|
const { signature, ...rest } = data;
|
|
170
140
|
const payload = JSON.stringify(rest);
|
|
171
141
|
try {
|
|
172
|
-
const crypto = await import(
|
|
142
|
+
const crypto = typeof window === "undefined" ? (await import(
|
|
143
|
+
/* webpackIgnore: true */
|
|
144
|
+
"crypto"
|
|
145
|
+
)).default : null;
|
|
146
|
+
if (!crypto || !crypto.createHmac) {
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
173
149
|
const expectedSignature = crypto.createHmac("sha256", this.secret).update(payload).digest("hex");
|
|
174
150
|
return signature === expectedSignature;
|
|
175
151
|
} catch (e) {
|
|
@@ -188,6 +164,9 @@ var ConfigManager = class {
|
|
|
188
164
|
this.config = initialConfig;
|
|
189
165
|
this.hooks = hooks;
|
|
190
166
|
this.registry = Registry.getInstance();
|
|
167
|
+
if (hooks) {
|
|
168
|
+
this.registry.setHooks(hooks);
|
|
169
|
+
}
|
|
191
170
|
this.loadFromRegistry();
|
|
192
171
|
}
|
|
193
172
|
getRegistry() {
|
|
@@ -195,6 +174,22 @@ var ConfigManager = class {
|
|
|
195
174
|
}
|
|
196
175
|
setHooks(hooks) {
|
|
197
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
|
+
}
|
|
198
193
|
}
|
|
199
194
|
loadFromRegistry() {
|
|
200
195
|
const instanceConfigs = this.registry.load();
|
|
@@ -220,19 +215,16 @@ var ConfigManager = class {
|
|
|
220
215
|
try {
|
|
221
216
|
const baseUrl = this.config.centralReportEndpoint || "https://ik-firewall.vercel.app";
|
|
222
217
|
const endpoint = `${baseUrl.replace(/\/$/, "")}/api/v1/sync`;
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
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) {
|
|
226
220
|
const success = await this.registerInstance(instanceId, registrationEmail);
|
|
227
221
|
if (success) {
|
|
228
222
|
this.hooks?.onStatus?.("\u2728 IK_SYNC: Zero-Config registration successful. License issued.");
|
|
229
223
|
return;
|
|
230
|
-
} else {
|
|
231
|
-
this.hooks?.onStatus?.("\u26A0\uFE0F IK_SYNC: Auto-registration failed. Continuing with local trial.");
|
|
232
224
|
}
|
|
233
225
|
}
|
|
234
226
|
this.hooks?.onStatus?.(`\u{1F4E1} IK_SYNC: Heartbeat to ${endpoint}...`);
|
|
235
|
-
const response = await fetch(`${endpoint}?licenseKey=${
|
|
227
|
+
const response = await fetch(`${endpoint}?licenseKey=${instanceConfig?.licenseKey || ""}&instanceId=${instanceId}&version=2.3.0`);
|
|
236
228
|
if (response.ok) {
|
|
237
229
|
const data = await response.json();
|
|
238
230
|
if (this.config.hmacSecret) {
|
|
@@ -252,9 +244,6 @@ var ConfigManager = class {
|
|
|
252
244
|
} else {
|
|
253
245
|
throw new Error(`Server responded with ${response.status}`);
|
|
254
246
|
}
|
|
255
|
-
if (!instanceConfig2?.firstUseDate) {
|
|
256
|
-
this.setInstanceConfig(instanceId, { firstUseDate: now.toISOString() });
|
|
257
|
-
}
|
|
258
247
|
} catch (error) {
|
|
259
248
|
console.error(`[IK_CONFIG] Remote sync failed (${error.message}). Falling open (Fail-Open mode).`);
|
|
260
249
|
this.setInstanceConfig(instanceId, {
|
|
@@ -262,17 +251,6 @@ var ConfigManager = class {
|
|
|
262
251
|
lastSyncDate: now.toISOString()
|
|
263
252
|
});
|
|
264
253
|
}
|
|
265
|
-
try {
|
|
266
|
-
const registry = this.registry;
|
|
267
|
-
if (registry.exists(registry.getUsageFilePath())) {
|
|
268
|
-
const aggregatedUsage = registry.loadUsage();
|
|
269
|
-
if (aggregatedUsage.length > 0) {
|
|
270
|
-
this.hooks?.onStatus?.(`\u{1F4CA} IK_SYNC: Found ${aggregatedUsage.length} pending usage reports. Preparation for Daily Flush...`);
|
|
271
|
-
this._pendingUsage = aggregatedUsage;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
} catch (e) {
|
|
275
|
-
}
|
|
276
254
|
}
|
|
277
255
|
getConfig() {
|
|
278
256
|
return { ...this.config };
|
|
@@ -1430,9 +1408,13 @@ var Orchestrator = class {
|
|
|
1430
1408
|
|
|
1431
1409
|
// src/UsageTracker.ts
|
|
1432
1410
|
var UsageTracker = class {
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
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;
|
|
1436
1418
|
hooks;
|
|
1437
1419
|
config;
|
|
1438
1420
|
constructor(config, hooks) {
|
|
@@ -1440,105 +1422,116 @@ var UsageTracker = class {
|
|
|
1440
1422
|
this.hooks = hooks;
|
|
1441
1423
|
}
|
|
1442
1424
|
/**
|
|
1443
|
-
* Record a single AI interaction
|
|
1425
|
+
* Record a single AI interaction and aggregate in memory
|
|
1444
1426
|
*/
|
|
1445
1427
|
async logInteraction(params) {
|
|
1446
|
-
const { instanceId, model, inputTokens, outputTokens, optimizedTokens = 0
|
|
1428
|
+
const { instanceId, model, inputTokens, outputTokens, optimizedTokens = 0 } = params;
|
|
1447
1429
|
const tokensSaved = optimizedTokens > 0 ? inputTokens - optimizedTokens : 0;
|
|
1448
|
-
const
|
|
1449
|
-
const
|
|
1450
|
-
const
|
|
1451
|
-
const
|
|
1452
|
-
|
|
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?.({
|
|
1453
1445
|
instanceId,
|
|
1454
1446
|
model_used: model,
|
|
1455
|
-
routingPath,
|
|
1447
|
+
routingPath: params.routingPath,
|
|
1456
1448
|
input_tokens: inputTokens,
|
|
1457
1449
|
output_tokens: outputTokens,
|
|
1458
1450
|
optimized_tokens: optimizedTokens,
|
|
1459
1451
|
tokens_saved: tokensSaved,
|
|
1460
|
-
cost_saved:
|
|
1461
|
-
commission_due:
|
|
1452
|
+
cost_saved: 0,
|
|
1453
|
+
commission_due: 0,
|
|
1462
1454
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1463
1455
|
is_local: model === "local",
|
|
1464
|
-
cq_score: cqScore,
|
|
1465
|
-
clientOrigin,
|
|
1466
|
-
trace
|
|
1467
|
-
};
|
|
1468
|
-
this.
|
|
1469
|
-
this.
|
|
1470
|
-
|
|
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
|
+
}
|
|
1471
1471
|
}
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
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
|
+
};
|
|
1476
1485
|
try {
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
existingUsage.push(data);
|
|
1484
|
-
registry.saveUsage(existingUsage);
|
|
1485
|
-
} catch (e) {
|
|
1486
|
-
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;
|
|
1487
1492
|
}
|
|
1488
1493
|
}
|
|
1489
|
-
|
|
1490
|
-
* Send buffered usage data to the central IK billing service
|
|
1491
|
-
*/
|
|
1492
|
-
async flush(aggregatedData) {
|
|
1493
|
-
const usageToFlush = aggregatedData || this.buffer;
|
|
1494
|
-
if (usageToFlush.length === 0) return;
|
|
1494
|
+
async sendBatch(payload) {
|
|
1495
1495
|
const baseUrl = this.config.get("centralReportEndpoint") || "https://ik-firewall.vercel.app";
|
|
1496
1496
|
const endpoint = `${baseUrl.replace(/\/$/, "")}/api/v1/report`;
|
|
1497
|
-
const hmacSecret = this.config.get("hmacSecret");
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
reportsByInstance[report.instanceId].push(report);
|
|
1504
|
-
}
|
|
1505
|
-
for (const [instanceId, reports] of Object.entries(reportsByInstance)) {
|
|
1506
|
-
const instanceConfig = this.config.getConfig().instanceConfigs?.[instanceId];
|
|
1507
|
-
const payload = {
|
|
1508
|
-
licenseKey: instanceConfig?.licenseKey || "",
|
|
1509
|
-
instanceId,
|
|
1510
|
-
reports: reports.map((r) => ({
|
|
1511
|
-
modelName: r.model_used,
|
|
1512
|
-
tokensIn: r.input_tokens,
|
|
1513
|
-
tokensOut: r.output_tokens,
|
|
1514
|
-
tokensSaved: r.tokens_saved,
|
|
1515
|
-
date: r.timestamp
|
|
1516
|
-
}))
|
|
1517
|
-
};
|
|
1518
|
-
if (hmacSecret) {
|
|
1519
|
-
const jsonStr = JSON.stringify(payload);
|
|
1520
|
-
const crypto = await import("crypto");
|
|
1521
|
-
payload.signature = crypto.createHmac("sha256", hmacSecret).update(jsonStr).digest("hex");
|
|
1522
|
-
}
|
|
1523
|
-
const response = await fetch(endpoint, {
|
|
1524
|
-
method: "POST",
|
|
1525
|
-
headers: { "Content-Type": "application/json" },
|
|
1526
|
-
body: JSON.stringify(payload)
|
|
1527
|
-
});
|
|
1528
|
-
if (!response.ok) {
|
|
1529
|
-
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");
|
|
1530
1503
|
}
|
|
1504
|
+
} catch (e) {
|
|
1531
1505
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
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) {
|
|
1535
1527
|
}
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1528
|
+
}
|
|
1529
|
+
if (this.hooks?.persistState) {
|
|
1530
|
+
await this.hooks.persistState(`ik_retry_${payload.timestamp}`, JSON.stringify(payload));
|
|
1538
1531
|
}
|
|
1539
1532
|
}
|
|
1540
1533
|
getBuffer() {
|
|
1541
|
-
return
|
|
1534
|
+
return Array.from(this.aggregationMap.values());
|
|
1542
1535
|
}
|
|
1543
1536
|
};
|
|
1544
1537
|
|
|
@@ -1813,20 +1806,20 @@ var IKFirewallCore = class _IKFirewallCore {
|
|
|
1813
1806
|
async logConsumption(params) {
|
|
1814
1807
|
return this.usageTracker.logInteraction(params);
|
|
1815
1808
|
}
|
|
1809
|
+
hydrated = false;
|
|
1816
1810
|
/**
|
|
1817
1811
|
* Primary Analysis Entry Point
|
|
1818
1812
|
*/
|
|
1819
1813
|
async analyze(input, provider, personaName = "professional", locale, instanceId) {
|
|
1814
|
+
if (!this.hydrated) {
|
|
1815
|
+
await this.configManager.hydrateFromHooks();
|
|
1816
|
+
this.hydrated = true;
|
|
1817
|
+
}
|
|
1820
1818
|
if (instanceId) {
|
|
1821
1819
|
this.configManager.ensureInstanceConfig(instanceId);
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
if (pendingUsage && pendingUsage.length > 0) {
|
|
1825
|
-
await this.usageTracker.flush(pendingUsage);
|
|
1826
|
-
this.configManager._pendingUsage = null;
|
|
1827
|
-
}
|
|
1820
|
+
this.configManager.syncRemoteConfig(instanceId).catch((err) => {
|
|
1821
|
+
console.warn("[IK_CORE] Heartbeat sync failed:", err);
|
|
1828
1822
|
});
|
|
1829
|
-
await syncPromise;
|
|
1830
1823
|
}
|
|
1831
1824
|
const mergedConfig = this.configManager.getMergedConfig(instanceId);
|
|
1832
1825
|
if (instanceId && mergedConfig?.isVerified === false) {
|