@kb-labs/rest-api-app 2.81.0 → 2.87.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +54 -18
- package/dist/index.js.map +1 -1
- package/package.json +21 -21
package/dist/index.js
CHANGED
|
@@ -27,6 +27,7 @@ import fastifyCors from '@fastify/cors';
|
|
|
27
27
|
import fastifyRateLimit from '@fastify/rate-limit';
|
|
28
28
|
import { ulid } from 'ulid';
|
|
29
29
|
import { randomUUID, createHash } from 'crypto';
|
|
30
|
+
import { TenantRateLimiter, getDefaultTenantTier } from '@kb-labs/core-tenant';
|
|
30
31
|
import { promises, existsSync, readFileSync } from 'fs';
|
|
31
32
|
import { findRepoRoot } from '@kb-labs/core-sys';
|
|
32
33
|
|
|
@@ -2904,7 +2905,7 @@ async function registerCacheRoutes(server, config, registry) {
|
|
|
2904
2905
|
}
|
|
2905
2906
|
});
|
|
2906
2907
|
}
|
|
2907
|
-
async function registerObservabilityRoutes(fastify, config, repoRoot, historicalMetrics,
|
|
2908
|
+
async function registerObservabilityRoutes(fastify, config, repoRoot, historicalMetrics, platform17) {
|
|
2908
2909
|
const basePath = normalizeBasePath(config.basePath);
|
|
2909
2910
|
const stateBrokerPaths = resolvePaths(basePath, "/observability/state-broker");
|
|
2910
2911
|
const systemMetricsPaths = resolvePaths(basePath, "/observability/system-metrics");
|
|
@@ -2951,7 +2952,7 @@ async function registerObservabilityRoutes(fastify, config, repoRoot, historical
|
|
|
2951
2952
|
}
|
|
2952
2953
|
};
|
|
2953
2954
|
} catch (error) {
|
|
2954
|
-
|
|
2955
|
+
platform17?.logger.error("Failed to fetch State Broker stats", error instanceof Error ? error : new Error(String(error)));
|
|
2955
2956
|
const isTimeout = error instanceof Error && error.name === "AbortError";
|
|
2956
2957
|
return reply.code(503).send({
|
|
2957
2958
|
ok: false,
|
|
@@ -2969,7 +2970,7 @@ async function registerObservabilityRoutes(fastify, config, repoRoot, historical
|
|
|
2969
2970
|
for (const path3 of systemMetricsPaths) {
|
|
2970
2971
|
fastify.get(path3, { schema: { tags: ["Observability"], summary: "System resource metrics" } }, async (_request, reply) => {
|
|
2971
2972
|
try {
|
|
2972
|
-
if (!
|
|
2973
|
+
if (!platform17?.cache) {
|
|
2973
2974
|
return reply.code(503).send({
|
|
2974
2975
|
ok: false,
|
|
2975
2976
|
error: {
|
|
@@ -2981,17 +2982,17 @@ async function registerObservabilityRoutes(fastify, config, repoRoot, historical
|
|
|
2981
2982
|
fastify.log.debug("Fetching system metrics from all instances");
|
|
2982
2983
|
const allMetrics = [];
|
|
2983
2984
|
try {
|
|
2984
|
-
if ("scan" in
|
|
2985
|
-
const keys = await
|
|
2985
|
+
if ("scan" in platform17.cache && typeof platform17.cache.scan === "function") {
|
|
2986
|
+
const keys = await platform17.cache.scan("system-metrics:*");
|
|
2986
2987
|
for (const key of keys) {
|
|
2987
|
-
const metrics = await
|
|
2988
|
+
const metrics = await platform17.cache.get(key);
|
|
2988
2989
|
if (metrics) {
|
|
2989
2990
|
allMetrics.push(metrics);
|
|
2990
2991
|
}
|
|
2991
2992
|
}
|
|
2992
2993
|
} else {
|
|
2993
2994
|
const currentInstanceId = hostname();
|
|
2994
|
-
const metrics = await
|
|
2995
|
+
const metrics = await platform17.cache.get(`system-metrics:${currentInstanceId}`);
|
|
2995
2996
|
if (metrics) {
|
|
2996
2997
|
allMetrics.push(metrics);
|
|
2997
2998
|
}
|
|
@@ -3000,7 +3001,7 @@ async function registerObservabilityRoutes(fastify, config, repoRoot, historical
|
|
|
3000
3001
|
} catch (scanError) {
|
|
3001
3002
|
fastify.log.warn({ err: scanError }, "Failed to scan platform.cache for system metrics");
|
|
3002
3003
|
const currentInstanceId = hostname();
|
|
3003
|
-
const metrics = await
|
|
3004
|
+
const metrics = await platform17.cache.get(`system-metrics:${currentInstanceId}`);
|
|
3004
3005
|
if (metrics) {
|
|
3005
3006
|
allMetrics.push(metrics);
|
|
3006
3007
|
}
|
|
@@ -3048,7 +3049,7 @@ async function registerObservabilityRoutes(fastify, config, repoRoot, historical
|
|
|
3048
3049
|
}
|
|
3049
3050
|
};
|
|
3050
3051
|
} catch (error) {
|
|
3051
|
-
|
|
3052
|
+
platform17?.logger.error("Failed to fetch system metrics", error instanceof Error ? error : new Error(String(error)));
|
|
3052
3053
|
return reply.code(500).send({
|
|
3053
3054
|
ok: false,
|
|
3054
3055
|
error: {
|
|
@@ -3146,7 +3147,7 @@ async function registerObservabilityRoutes(fastify, config, repoRoot, historical
|
|
|
3146
3147
|
}
|
|
3147
3148
|
};
|
|
3148
3149
|
} catch (error) {
|
|
3149
|
-
|
|
3150
|
+
platform17?.logger.error("Failed to query historical metrics", error instanceof Error ? error : new Error(String(error)), { query });
|
|
3150
3151
|
return reply.code(500).send({
|
|
3151
3152
|
ok: false,
|
|
3152
3153
|
error: {
|
|
@@ -3237,7 +3238,7 @@ async function registerObservabilityRoutes(fastify, config, repoRoot, historical
|
|
|
3237
3238
|
}
|
|
3238
3239
|
};
|
|
3239
3240
|
} catch (error) {
|
|
3240
|
-
|
|
3241
|
+
platform17?.logger.error("Failed to query heatmap data", error instanceof Error ? error : new Error(String(error)), { query });
|
|
3241
3242
|
return reply.code(500).send({
|
|
3242
3243
|
ok: false,
|
|
3243
3244
|
error: {
|
|
@@ -3271,7 +3272,7 @@ async function registerObservabilityRoutes(fastify, config, repoRoot, historical
|
|
|
3271
3272
|
}
|
|
3272
3273
|
}
|
|
3273
3274
|
}, async (request, reply) => {
|
|
3274
|
-
if (!
|
|
3275
|
+
if (!platform17?.llm) {
|
|
3275
3276
|
return reply.code(503).send({
|
|
3276
3277
|
ok: false,
|
|
3277
3278
|
error: {
|
|
@@ -3337,15 +3338,15 @@ Provide a clear, actionable response based on the data above. Include:
|
|
|
3337
3338
|
|
|
3338
3339
|
Be concise but thorough. Use markdown formatting.`;
|
|
3339
3340
|
fastify.log.debug({ question: body.question, contextLength: contextText.length }, "Calling LLM for insights");
|
|
3340
|
-
const result = await
|
|
3341
|
+
const result = await platform17.llm.complete(prompt, {
|
|
3341
3342
|
systemPrompt: "You are a DevOps and SRE expert assistant. Analyze system metrics and provide actionable insights. Be concise, technical, and helpful.",
|
|
3342
3343
|
temperature: 0.7,
|
|
3343
3344
|
maxTokens: 1e3
|
|
3344
3345
|
});
|
|
3345
3346
|
const totalTokens = result.usage.promptTokens + result.usage.completionTokens;
|
|
3346
3347
|
fastify.log.debug({ tokensUsed: totalTokens }, "LLM response received for insights");
|
|
3347
|
-
if (
|
|
3348
|
-
|
|
3348
|
+
if (platform17.analytics) {
|
|
3349
|
+
platform17.analytics.track("ai_insights.chat", {
|
|
3349
3350
|
questionLength: body.question.length,
|
|
3350
3351
|
contextIncluded: Object.keys(contextConfig).filter((k) => contextConfig[k]),
|
|
3351
3352
|
timeRange: contextConfig.timeRange,
|
|
@@ -3374,9 +3375,9 @@ Be concise but thorough. Use markdown formatting.`;
|
|
|
3374
3375
|
}
|
|
3375
3376
|
};
|
|
3376
3377
|
} catch (error) {
|
|
3377
|
-
|
|
3378
|
-
if (
|
|
3379
|
-
|
|
3378
|
+
platform17?.logger.error("Failed to generate insights", error instanceof Error ? error : new Error(String(error)));
|
|
3379
|
+
if (platform17?.analytics) {
|
|
3380
|
+
platform17.analytics.track("ai_insights.error", {
|
|
3380
3381
|
error: error instanceof Error ? error.message : "Unknown error",
|
|
3381
3382
|
questionLength: request.body?.question?.length ?? 0
|
|
3382
3383
|
}).catch(() => {
|
|
@@ -6224,11 +6225,46 @@ function registerRequestTimeoutGuard(server, config) {
|
|
|
6224
6225
|
});
|
|
6225
6226
|
}
|
|
6226
6227
|
|
|
6228
|
+
// src/middleware/rate-limit.ts
|
|
6229
|
+
function extractTenantId(request) {
|
|
6230
|
+
return request.headers["x-tenant-id"] || process.env.KB_TENANT_ID || "default";
|
|
6231
|
+
}
|
|
6232
|
+
function createRateLimitMiddleware(rateLimiter) {
|
|
6233
|
+
return async function rateLimitMiddleware(request, reply) {
|
|
6234
|
+
const tenantId = extractTenantId(request);
|
|
6235
|
+
const result = await rateLimiter.checkLimit(tenantId, "requests");
|
|
6236
|
+
reply.header("X-RateLimit-Limit", result.limit);
|
|
6237
|
+
reply.header("X-RateLimit-Remaining", result.remaining);
|
|
6238
|
+
reply.header("X-RateLimit-Reset", result.resetAt);
|
|
6239
|
+
if (!result.allowed) {
|
|
6240
|
+
const retryAfter = Math.ceil((result.resetAt - Date.now()) / 1e3);
|
|
6241
|
+
reply.header("Retry-After", retryAfter);
|
|
6242
|
+
return reply.code(429).send({
|
|
6243
|
+
error: "RATE_LIMIT_EXCEEDED",
|
|
6244
|
+
message: "Too many requests. Please try again later.",
|
|
6245
|
+
retryAfter,
|
|
6246
|
+
limit: result.limit,
|
|
6247
|
+
resetAt: new Date(result.resetAt).toISOString()
|
|
6248
|
+
});
|
|
6249
|
+
}
|
|
6250
|
+
request.tenantId = tenantId;
|
|
6251
|
+
};
|
|
6252
|
+
}
|
|
6253
|
+
|
|
6254
|
+
// src/middleware/tenant-rate-limit.ts
|
|
6255
|
+
function registerTenantRateLimitMiddleware(server, cache) {
|
|
6256
|
+
const limiter = new TenantRateLimiter(cache);
|
|
6257
|
+
limiter.setTier("default", getDefaultTenantTier());
|
|
6258
|
+
const handler = createRateLimitMiddleware(limiter);
|
|
6259
|
+
server.addHook("preHandler", handler);
|
|
6260
|
+
}
|
|
6261
|
+
|
|
6227
6262
|
// src/middleware/index.ts
|
|
6228
6263
|
function registerMiddleware(server, config) {
|
|
6229
6264
|
registerStartupGuard(server, config);
|
|
6230
6265
|
registerSecurityHeadersMiddleware(server);
|
|
6231
6266
|
registerRequestIdMiddleware(server);
|
|
6267
|
+
registerTenantRateLimitMiddleware(server, platform.cache);
|
|
6232
6268
|
registerMockModeMiddleware(server, config);
|
|
6233
6269
|
registerCacheMiddleware(server);
|
|
6234
6270
|
registerRequestTimeoutGuard(server, config);
|