midnight-mcp 0.0.1 → 0.0.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/README.md +40 -20
- package/dist/pipeline/github.d.ts +18 -1
- package/dist/pipeline/github.js +132 -20
- package/dist/server.js +29 -7
- package/dist/tools/health.d.ts +91 -0
- package/dist/tools/health.js +91 -0
- package/dist/tools/index.d.ts +30 -4
- package/dist/tools/index.js +10 -3
- package/dist/tools/search.d.ts +3 -45
- package/dist/tools/search.js +101 -13
- package/dist/utils/cache.d.ts +77 -0
- package/dist/utils/cache.js +172 -0
- package/dist/utils/errors.d.ts +45 -0
- package/dist/utils/errors.js +95 -0
- package/dist/utils/health.d.ts +29 -0
- package/dist/utils/health.js +132 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/logger.d.ts +30 -1
- package/dist/utils/logger.js +68 -3
- package/dist/utils/rate-limit.d.ts +61 -0
- package/dist/utils/rate-limit.js +148 -0
- package/dist/utils/validation.d.ts +52 -0
- package/dist/utils/validation.js +255 -0
- package/package.json +3 -2
- package/dist/db/index.d.ts.map +0 -1
- package/dist/db/index.js.map +0 -1
- package/dist/db/vectorStore.d.ts.map +0 -1
- package/dist/db/vectorStore.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/pipeline/embeddings.d.ts.map +0 -1
- package/dist/pipeline/embeddings.js.map +0 -1
- package/dist/pipeline/github.d.ts.map +0 -1
- package/dist/pipeline/github.js.map +0 -1
- package/dist/pipeline/index.d.ts.map +0 -1
- package/dist/pipeline/index.js.map +0 -1
- package/dist/pipeline/indexer.d.ts.map +0 -1
- package/dist/pipeline/indexer.js.map +0 -1
- package/dist/pipeline/parser.d.ts.map +0 -1
- package/dist/pipeline/parser.js.map +0 -1
- package/dist/pipeline/releases.d.ts.map +0 -1
- package/dist/pipeline/releases.js.map +0 -1
- package/dist/pipeline/repository.d.ts.map +0 -1
- package/dist/pipeline/repository.js.map +0 -1
- package/dist/prompts/index.d.ts.map +0 -1
- package/dist/prompts/index.js.map +0 -1
- package/dist/prompts/templates.d.ts.map +0 -1
- package/dist/prompts/templates.js.map +0 -1
- package/dist/resources/code.d.ts.map +0 -1
- package/dist/resources/code.js.map +0 -1
- package/dist/resources/docs.d.ts.map +0 -1
- package/dist/resources/docs.js.map +0 -1
- package/dist/resources/index.d.ts.map +0 -1
- package/dist/resources/index.js.map +0 -1
- package/dist/resources/schemas.d.ts.map +0 -1
- package/dist/resources/schemas.js.map +0 -1
- package/dist/scripts/index-repos.d.ts.map +0 -1
- package/dist/scripts/index-repos.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/tools/analyze.d.ts.map +0 -1
- package/dist/tools/analyze.js.map +0 -1
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/repository.d.ts.map +0 -1
- package/dist/tools/repository.js.map +0 -1
- package/dist/tools/search.d.ts.map +0 -1
- package/dist/tools/search.js.map +0 -1
- package/dist/utils/config.d.ts.map +0 -1
- package/dist/utils/config.js.map +0 -1
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js.map +0 -1
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health check utilities for MCP server monitoring
|
|
3
|
+
*/
|
|
4
|
+
import { githubClient } from "../pipeline/index.js";
|
|
5
|
+
// Track server start time
|
|
6
|
+
const startTime = Date.now();
|
|
7
|
+
// Get package version
|
|
8
|
+
const VERSION = process.env.npm_package_version || "0.0.3";
|
|
9
|
+
/**
|
|
10
|
+
* Check if GitHub API is accessible
|
|
11
|
+
*/
|
|
12
|
+
async function checkGitHubAPI() {
|
|
13
|
+
const start = Date.now();
|
|
14
|
+
try {
|
|
15
|
+
// Try to get rate limit info (lightweight API call)
|
|
16
|
+
const rateLimit = await githubClient.getRateLimit();
|
|
17
|
+
const latency = Date.now() - start;
|
|
18
|
+
if (rateLimit.remaining < 100) {
|
|
19
|
+
return {
|
|
20
|
+
status: "warn",
|
|
21
|
+
message: `Rate limit low: ${rateLimit.remaining}/${rateLimit.limit} remaining`,
|
|
22
|
+
latency,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
status: "pass",
|
|
27
|
+
message: `Rate limit: ${rateLimit.remaining}/${rateLimit.limit}`,
|
|
28
|
+
latency,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
return {
|
|
33
|
+
status: "fail",
|
|
34
|
+
message: `GitHub API error: ${error instanceof Error ? error.message : String(error)}`,
|
|
35
|
+
latency: Date.now() - start,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Check if ChromaDB is accessible (optional dependency)
|
|
41
|
+
*/
|
|
42
|
+
async function checkVectorStore() {
|
|
43
|
+
try {
|
|
44
|
+
// Import dynamically to handle optional dependency
|
|
45
|
+
const { vectorStore } = await import("../db/index.js");
|
|
46
|
+
// Check if vector store is initialized
|
|
47
|
+
if (vectorStore) {
|
|
48
|
+
return {
|
|
49
|
+
status: "pass",
|
|
50
|
+
message: "Vector store available",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
status: "warn",
|
|
55
|
+
message: "Vector store not initialized (semantic search unavailable)",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return {
|
|
60
|
+
status: "warn",
|
|
61
|
+
message: "Vector store not configured (semantic search unavailable)",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check memory usage
|
|
67
|
+
*/
|
|
68
|
+
function checkMemory() {
|
|
69
|
+
const usage = process.memoryUsage();
|
|
70
|
+
const heapUsedMB = Math.round(usage.heapUsed / 1024 / 1024);
|
|
71
|
+
const heapTotalMB = Math.round(usage.heapTotal / 1024 / 1024);
|
|
72
|
+
const percentUsed = Math.round((usage.heapUsed / usage.heapTotal) * 100);
|
|
73
|
+
if (percentUsed > 90) {
|
|
74
|
+
return {
|
|
75
|
+
status: "fail",
|
|
76
|
+
message: `High memory usage: ${heapUsedMB}MB/${heapTotalMB}MB (${percentUsed}%)`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (percentUsed > 75) {
|
|
80
|
+
return {
|
|
81
|
+
status: "warn",
|
|
82
|
+
message: `Elevated memory usage: ${heapUsedMB}MB/${heapTotalMB}MB (${percentUsed}%)`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
status: "pass",
|
|
87
|
+
message: `Memory: ${heapUsedMB}MB/${heapTotalMB}MB (${percentUsed}%)`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Perform a full health check
|
|
92
|
+
*/
|
|
93
|
+
export async function getHealthStatus() {
|
|
94
|
+
const checks = [];
|
|
95
|
+
// Run all health checks in parallel
|
|
96
|
+
const [githubCheck, vectorCheck] = await Promise.all([
|
|
97
|
+
checkGitHubAPI(),
|
|
98
|
+
checkVectorStore(),
|
|
99
|
+
]);
|
|
100
|
+
const memoryCheck = checkMemory();
|
|
101
|
+
checks.push({ name: "github_api", ...githubCheck }, { name: "vector_store", ...vectorCheck }, { name: "memory", ...memoryCheck });
|
|
102
|
+
// Determine overall status
|
|
103
|
+
const hasFailure = checks.some((c) => c.status === "fail");
|
|
104
|
+
const hasWarning = checks.some((c) => c.status === "warn");
|
|
105
|
+
let status = "healthy";
|
|
106
|
+
if (hasFailure) {
|
|
107
|
+
status = "unhealthy";
|
|
108
|
+
}
|
|
109
|
+
else if (hasWarning) {
|
|
110
|
+
status = "degraded";
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
status,
|
|
114
|
+
timestamp: new Date().toISOString(),
|
|
115
|
+
version: VERSION,
|
|
116
|
+
uptime: Math.round((Date.now() - startTime) / 1000),
|
|
117
|
+
checks,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get a quick health check (no external calls)
|
|
122
|
+
*/
|
|
123
|
+
export function getQuickHealthStatus() {
|
|
124
|
+
return {
|
|
125
|
+
status: "healthy",
|
|
126
|
+
timestamp: new Date().toISOString(),
|
|
127
|
+
version: VERSION,
|
|
128
|
+
uptime: Math.round((Date.now() - startTime) / 1000),
|
|
129
|
+
checks: [{ name: "server", status: "pass" }],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=health.js.map
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -2,4 +2,13 @@ export { config } from "./config.js";
|
|
|
2
2
|
export type { Config, RepositoryConfig } from "./config.js";
|
|
3
3
|
export { DEFAULT_REPOSITORIES } from "./config.js";
|
|
4
4
|
export { logger } from "./logger.js";
|
|
5
|
+
export { MCPError, ErrorCodes, createUserError, formatErrorResponse, withErrorHandling, } from "./errors.js";
|
|
6
|
+
export { validateQuery, validateRepository, validatePath, validateRef, validateNumber, validateToolArgs, sanitizeString, } from "./validation.js";
|
|
7
|
+
export type { ValidationResult } from "./validation.js";
|
|
8
|
+
export { getHealthStatus, getQuickHealthStatus } from "./health.js";
|
|
9
|
+
export type { HealthStatus } from "./health.js";
|
|
10
|
+
export { updateRateLimitFromHeaders, updateRateLimit, getRateLimitStatus, shouldProceedWithRequest, getTimeUntilReset, formatRateLimitStatus, decrementRemaining, } from "./rate-limit.js";
|
|
11
|
+
export type { RateLimitInfo, RateLimitStatus } from "./rate-limit.js";
|
|
12
|
+
export { Cache, createCacheKey, searchCache, fileCache, metadataCache, pruneAllCaches, } from "./cache.js";
|
|
13
|
+
export type { CacheOptions, CacheEntry, CacheStats } from "./cache.js";
|
|
5
14
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/utils/index.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
export { config } from "./config.js";
|
|
2
2
|
export { DEFAULT_REPOSITORIES } from "./config.js";
|
|
3
3
|
export { logger } from "./logger.js";
|
|
4
|
+
export { MCPError, ErrorCodes, createUserError, formatErrorResponse, withErrorHandling, } from "./errors.js";
|
|
5
|
+
// Validation utilities
|
|
6
|
+
export { validateQuery, validateRepository, validatePath, validateRef, validateNumber, validateToolArgs, sanitizeString, } from "./validation.js";
|
|
7
|
+
// Health check utilities
|
|
8
|
+
export { getHealthStatus, getQuickHealthStatus } from "./health.js";
|
|
9
|
+
// Rate limit tracking
|
|
10
|
+
export { updateRateLimitFromHeaders, updateRateLimit, getRateLimitStatus, shouldProceedWithRequest, getTimeUntilReset, formatRateLimitStatus, decrementRemaining, } from "./rate-limit.js";
|
|
11
|
+
// Caching utilities
|
|
12
|
+
export { Cache, createCacheKey, searchCache, fileCache, metadataCache, pruneAllCaches, } from "./cache.js";
|
|
4
13
|
//# sourceMappingURL=index.js.map
|
package/dist/utils/logger.d.ts
CHANGED
|
@@ -1,13 +1,42 @@
|
|
|
1
1
|
type LogLevel = "debug" | "info" | "warn" | "error";
|
|
2
|
+
type LogFormat = "text" | "json";
|
|
2
3
|
declare class Logger {
|
|
3
4
|
private level;
|
|
4
|
-
|
|
5
|
+
private format;
|
|
6
|
+
private service;
|
|
7
|
+
constructor(level?: LogLevel, format?: LogFormat, service?: string);
|
|
8
|
+
/**
|
|
9
|
+
* Set log format at runtime
|
|
10
|
+
*/
|
|
11
|
+
setFormat(format: LogFormat): void;
|
|
12
|
+
/**
|
|
13
|
+
* Set log level at runtime
|
|
14
|
+
*/
|
|
15
|
+
setLevel(level: LogLevel): void;
|
|
5
16
|
private shouldLog;
|
|
17
|
+
private formatTextMessage;
|
|
18
|
+
private formatJsonMessage;
|
|
6
19
|
private formatMessage;
|
|
7
20
|
debug(message: string, meta?: object): void;
|
|
8
21
|
info(message: string, meta?: object): void;
|
|
9
22
|
warn(message: string, meta?: object): void;
|
|
10
23
|
error(message: string, meta?: object): void;
|
|
24
|
+
/**
|
|
25
|
+
* Create a child logger with additional context
|
|
26
|
+
*/
|
|
27
|
+
child(context: object): ChildLogger;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Child logger that includes additional context in all log messages
|
|
31
|
+
*/
|
|
32
|
+
declare class ChildLogger {
|
|
33
|
+
private parent;
|
|
34
|
+
private context;
|
|
35
|
+
constructor(parent: Logger, context: object);
|
|
36
|
+
debug(message: string, meta?: object): void;
|
|
37
|
+
info(message: string, meta?: object): void;
|
|
38
|
+
warn(message: string, meta?: object): void;
|
|
39
|
+
error(message: string, meta?: object): void;
|
|
11
40
|
}
|
|
12
41
|
export declare const logger: Logger;
|
|
13
42
|
export {};
|
package/dist/utils/logger.js
CHANGED
|
@@ -7,17 +7,51 @@ const LOG_LEVELS = {
|
|
|
7
7
|
};
|
|
8
8
|
class Logger {
|
|
9
9
|
level;
|
|
10
|
-
|
|
10
|
+
format;
|
|
11
|
+
service;
|
|
12
|
+
constructor(level = "info", format = "text", service = "midnight-mcp") {
|
|
13
|
+
this.level = level;
|
|
14
|
+
this.format = format;
|
|
15
|
+
this.service = service;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Set log format at runtime
|
|
19
|
+
*/
|
|
20
|
+
setFormat(format) {
|
|
21
|
+
this.format = format;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Set log level at runtime
|
|
25
|
+
*/
|
|
26
|
+
setLevel(level) {
|
|
11
27
|
this.level = level;
|
|
12
28
|
}
|
|
13
29
|
shouldLog(level) {
|
|
14
30
|
return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
|
|
15
31
|
}
|
|
16
|
-
|
|
32
|
+
formatTextMessage(level, message, meta) {
|
|
17
33
|
const timestamp = new Date().toISOString();
|
|
18
34
|
const metaStr = meta ? ` ${JSON.stringify(meta)}` : "";
|
|
19
35
|
return `[${timestamp}] [${level.toUpperCase()}] ${message}${metaStr}`;
|
|
20
36
|
}
|
|
37
|
+
formatJsonMessage(level, message, meta) {
|
|
38
|
+
const entry = {
|
|
39
|
+
timestamp: new Date().toISOString(),
|
|
40
|
+
level,
|
|
41
|
+
message,
|
|
42
|
+
service: this.service,
|
|
43
|
+
};
|
|
44
|
+
if (meta) {
|
|
45
|
+
entry.meta = meta;
|
|
46
|
+
}
|
|
47
|
+
return JSON.stringify(entry);
|
|
48
|
+
}
|
|
49
|
+
formatMessage(level, message, meta) {
|
|
50
|
+
if (this.format === "json") {
|
|
51
|
+
return this.formatJsonMessage(level, message, meta);
|
|
52
|
+
}
|
|
53
|
+
return this.formatTextMessage(level, message, meta);
|
|
54
|
+
}
|
|
21
55
|
debug(message, meta) {
|
|
22
56
|
if (this.shouldLog("debug")) {
|
|
23
57
|
console.error(this.formatMessage("debug", message, meta));
|
|
@@ -38,6 +72,37 @@ class Logger {
|
|
|
38
72
|
console.error(this.formatMessage("error", message, meta));
|
|
39
73
|
}
|
|
40
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Create a child logger with additional context
|
|
77
|
+
*/
|
|
78
|
+
child(context) {
|
|
79
|
+
return new ChildLogger(this, context);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Child logger that includes additional context in all log messages
|
|
84
|
+
*/
|
|
85
|
+
class ChildLogger {
|
|
86
|
+
parent;
|
|
87
|
+
context;
|
|
88
|
+
constructor(parent, context) {
|
|
89
|
+
this.parent = parent;
|
|
90
|
+
this.context = context;
|
|
91
|
+
}
|
|
92
|
+
debug(message, meta) {
|
|
93
|
+
this.parent.debug(message, { ...this.context, ...meta });
|
|
94
|
+
}
|
|
95
|
+
info(message, meta) {
|
|
96
|
+
this.parent.info(message, { ...this.context, ...meta });
|
|
97
|
+
}
|
|
98
|
+
warn(message, meta) {
|
|
99
|
+
this.parent.warn(message, { ...this.context, ...meta });
|
|
100
|
+
}
|
|
101
|
+
error(message, meta) {
|
|
102
|
+
this.parent.error(message, { ...this.context, ...meta });
|
|
103
|
+
}
|
|
41
104
|
}
|
|
42
|
-
|
|
105
|
+
// Determine log format from environment
|
|
106
|
+
const logFormat = process.env.LOG_FORMAT === "json" ? "json" : "text";
|
|
107
|
+
export const logger = new Logger(config.logLevel, logFormat);
|
|
43
108
|
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate limit tracking and management utilities
|
|
3
|
+
* Tracks GitHub API rate limits and warns before hitting limits
|
|
4
|
+
*/
|
|
5
|
+
export interface RateLimitInfo {
|
|
6
|
+
limit: number;
|
|
7
|
+
remaining: number;
|
|
8
|
+
reset: Date;
|
|
9
|
+
used: number;
|
|
10
|
+
}
|
|
11
|
+
export interface RateLimitStatus {
|
|
12
|
+
isLimited: boolean;
|
|
13
|
+
isWarning: boolean;
|
|
14
|
+
remaining: number;
|
|
15
|
+
limit: number;
|
|
16
|
+
resetAt: Date;
|
|
17
|
+
percentUsed: number;
|
|
18
|
+
message: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Update rate limit info from API response headers
|
|
22
|
+
*/
|
|
23
|
+
export declare function updateRateLimitFromHeaders(headers: Record<string, string | undefined>): void;
|
|
24
|
+
/**
|
|
25
|
+
* Update rate limit info directly
|
|
26
|
+
*/
|
|
27
|
+
export declare function updateRateLimit(info: RateLimitInfo): void;
|
|
28
|
+
/**
|
|
29
|
+
* Get current rate limit status
|
|
30
|
+
*/
|
|
31
|
+
export declare function getRateLimitStatus(): RateLimitStatus;
|
|
32
|
+
/**
|
|
33
|
+
* Check if we should proceed with an API call
|
|
34
|
+
* Returns true if safe to proceed, false if we should wait/fail
|
|
35
|
+
*/
|
|
36
|
+
export declare function shouldProceedWithRequest(): {
|
|
37
|
+
proceed: boolean;
|
|
38
|
+
reason?: string;
|
|
39
|
+
waitMs?: number;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Get time until rate limit resets
|
|
43
|
+
*/
|
|
44
|
+
export declare function getTimeUntilReset(): number;
|
|
45
|
+
/**
|
|
46
|
+
* Check if cached rate limit info is stale
|
|
47
|
+
*/
|
|
48
|
+
export declare function isRateLimitStale(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Get cached rate limit info
|
|
51
|
+
*/
|
|
52
|
+
export declare function getCachedRateLimit(): RateLimitInfo | null;
|
|
53
|
+
/**
|
|
54
|
+
* Decrement remaining count (for optimistic tracking)
|
|
55
|
+
*/
|
|
56
|
+
export declare function decrementRemaining(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Format rate limit status for display
|
|
59
|
+
*/
|
|
60
|
+
export declare function formatRateLimitStatus(): string;
|
|
61
|
+
//# sourceMappingURL=rate-limit.d.ts.map
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate limit tracking and management utilities
|
|
3
|
+
* Tracks GitHub API rate limits and warns before hitting limits
|
|
4
|
+
*/
|
|
5
|
+
import { logger } from "./logger.js";
|
|
6
|
+
// Warning threshold - warn when this percentage of rate limit is used
|
|
7
|
+
const WARNING_THRESHOLD = 0.8; // 80%
|
|
8
|
+
// Critical threshold - consider limited when this percentage is used
|
|
9
|
+
const CRITICAL_THRESHOLD = 0.95; // 95%
|
|
10
|
+
// Cached rate limit info
|
|
11
|
+
let cachedRateLimit = null;
|
|
12
|
+
let lastUpdate = 0;
|
|
13
|
+
const CACHE_TTL = 60 * 1000; // 1 minute cache
|
|
14
|
+
/**
|
|
15
|
+
* Update rate limit info from API response headers
|
|
16
|
+
*/
|
|
17
|
+
export function updateRateLimitFromHeaders(headers) {
|
|
18
|
+
const limit = parseInt(headers["x-ratelimit-limit"] || "5000", 10);
|
|
19
|
+
const remaining = parseInt(headers["x-ratelimit-remaining"] || "5000", 10);
|
|
20
|
+
const resetTimestamp = parseInt(headers["x-ratelimit-reset"] || "0", 10);
|
|
21
|
+
cachedRateLimit = {
|
|
22
|
+
limit,
|
|
23
|
+
remaining,
|
|
24
|
+
reset: new Date(resetTimestamp * 1000),
|
|
25
|
+
used: limit - remaining,
|
|
26
|
+
};
|
|
27
|
+
lastUpdate = Date.now();
|
|
28
|
+
// Log warning if approaching limit
|
|
29
|
+
const percentUsed = (cachedRateLimit.used / cachedRateLimit.limit) * 100;
|
|
30
|
+
if (percentUsed >= WARNING_THRESHOLD * 100) {
|
|
31
|
+
logger.warn("GitHub API rate limit warning", {
|
|
32
|
+
remaining: cachedRateLimit.remaining,
|
|
33
|
+
limit: cachedRateLimit.limit,
|
|
34
|
+
percentUsed: Math.round(percentUsed),
|
|
35
|
+
resetAt: cachedRateLimit.reset.toISOString(),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Update rate limit info directly
|
|
41
|
+
*/
|
|
42
|
+
export function updateRateLimit(info) {
|
|
43
|
+
cachedRateLimit = info;
|
|
44
|
+
lastUpdate = Date.now();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get current rate limit status
|
|
48
|
+
*/
|
|
49
|
+
export function getRateLimitStatus() {
|
|
50
|
+
if (!cachedRateLimit) {
|
|
51
|
+
return {
|
|
52
|
+
isLimited: false,
|
|
53
|
+
isWarning: false,
|
|
54
|
+
remaining: 5000,
|
|
55
|
+
limit: 5000,
|
|
56
|
+
resetAt: new Date(),
|
|
57
|
+
percentUsed: 0,
|
|
58
|
+
message: "Rate limit info not yet available",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const percentUsed = cachedRateLimit.used / cachedRateLimit.limit;
|
|
62
|
+
const isWarning = percentUsed >= WARNING_THRESHOLD;
|
|
63
|
+
const isLimited = percentUsed >= CRITICAL_THRESHOLD || cachedRateLimit.remaining <= 10;
|
|
64
|
+
let message;
|
|
65
|
+
if (isLimited) {
|
|
66
|
+
const minutesUntilReset = Math.ceil((cachedRateLimit.reset.getTime() - Date.now()) / 60000);
|
|
67
|
+
message = `Rate limited! Resets in ${minutesUntilReset} minutes`;
|
|
68
|
+
}
|
|
69
|
+
else if (isWarning) {
|
|
70
|
+
message = `Warning: ${cachedRateLimit.remaining} API calls remaining (${Math.round(percentUsed * 100)}% used)`;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
message = `${cachedRateLimit.remaining}/${cachedRateLimit.limit} API calls remaining`;
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
isLimited,
|
|
77
|
+
isWarning,
|
|
78
|
+
remaining: cachedRateLimit.remaining,
|
|
79
|
+
limit: cachedRateLimit.limit,
|
|
80
|
+
resetAt: cachedRateLimit.reset,
|
|
81
|
+
percentUsed: Math.round(percentUsed * 100),
|
|
82
|
+
message,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Check if we should proceed with an API call
|
|
87
|
+
* Returns true if safe to proceed, false if we should wait/fail
|
|
88
|
+
*/
|
|
89
|
+
export function shouldProceedWithRequest() {
|
|
90
|
+
if (!cachedRateLimit) {
|
|
91
|
+
return { proceed: true };
|
|
92
|
+
}
|
|
93
|
+
if (cachedRateLimit.remaining <= 10) {
|
|
94
|
+
const waitMs = Math.max(0, cachedRateLimit.reset.getTime() - Date.now());
|
|
95
|
+
return {
|
|
96
|
+
proceed: false,
|
|
97
|
+
reason: `Rate limit nearly exhausted (${cachedRateLimit.remaining} remaining)`,
|
|
98
|
+
waitMs,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return { proceed: true };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get time until rate limit resets
|
|
105
|
+
*/
|
|
106
|
+
export function getTimeUntilReset() {
|
|
107
|
+
if (!cachedRateLimit) {
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
return Math.max(0, cachedRateLimit.reset.getTime() - Date.now());
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Check if cached rate limit info is stale
|
|
114
|
+
*/
|
|
115
|
+
export function isRateLimitStale() {
|
|
116
|
+
return Date.now() - lastUpdate > CACHE_TTL;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get cached rate limit info
|
|
120
|
+
*/
|
|
121
|
+
export function getCachedRateLimit() {
|
|
122
|
+
return cachedRateLimit;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Decrement remaining count (for optimistic tracking)
|
|
126
|
+
*/
|
|
127
|
+
export function decrementRemaining() {
|
|
128
|
+
if (cachedRateLimit && cachedRateLimit.remaining > 0) {
|
|
129
|
+
cachedRateLimit.remaining--;
|
|
130
|
+
cachedRateLimit.used++;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Format rate limit status for display
|
|
135
|
+
*/
|
|
136
|
+
export function formatRateLimitStatus() {
|
|
137
|
+
const status = getRateLimitStatus();
|
|
138
|
+
if (status.isLimited) {
|
|
139
|
+
return `⛔ ${status.message}`;
|
|
140
|
+
}
|
|
141
|
+
else if (status.isWarning) {
|
|
142
|
+
return `⚠️ ${status.message}`;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
return `✅ ${status.message}`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=rate-limit.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input validation and sanitization utilities
|
|
3
|
+
* Protects against injection attacks and malformed inputs
|
|
4
|
+
*/
|
|
5
|
+
export interface ValidationResult {
|
|
6
|
+
isValid: boolean;
|
|
7
|
+
sanitized: string;
|
|
8
|
+
warnings: string[];
|
|
9
|
+
errors: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Sanitize a string by removing dangerous patterns
|
|
13
|
+
*/
|
|
14
|
+
export declare function sanitizeString(input: string, maxLength?: number): string;
|
|
15
|
+
/**
|
|
16
|
+
* Validate and sanitize a search query
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateQuery(query: unknown): ValidationResult;
|
|
19
|
+
/**
|
|
20
|
+
* Validate and sanitize a repository name
|
|
21
|
+
*/
|
|
22
|
+
export declare function validateRepository(repo: unknown): ValidationResult;
|
|
23
|
+
/**
|
|
24
|
+
* Validate and sanitize a file path
|
|
25
|
+
*/
|
|
26
|
+
export declare function validatePath(path: unknown): ValidationResult;
|
|
27
|
+
/**
|
|
28
|
+
* Validate and sanitize a git ref (branch, tag, or commit)
|
|
29
|
+
*/
|
|
30
|
+
export declare function validateRef(ref: unknown): ValidationResult;
|
|
31
|
+
/**
|
|
32
|
+
* Validate a numeric input within bounds
|
|
33
|
+
*/
|
|
34
|
+
export declare function validateNumber(value: unknown, options: {
|
|
35
|
+
min?: number;
|
|
36
|
+
max?: number;
|
|
37
|
+
defaultValue: number;
|
|
38
|
+
}): {
|
|
39
|
+
isValid: boolean;
|
|
40
|
+
value: number;
|
|
41
|
+
error?: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Validate tool arguments with automatic sanitization
|
|
45
|
+
*/
|
|
46
|
+
export declare function validateToolArgs<T extends Record<string, unknown>>(args: T, validators: Partial<Record<keyof T, (value: unknown) => ValidationResult>>): {
|
|
47
|
+
isValid: boolean;
|
|
48
|
+
sanitized: Partial<T>;
|
|
49
|
+
errors: string[];
|
|
50
|
+
warnings: string[];
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=validation.d.ts.map
|