gthinking 1.2.1 → 2.1.1
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/.eslintrc.js +34 -0
- package/ANALYSIS_SUMMARY.md +363 -0
- package/README.md +230 -245
- package/dist/analysis/analysis-engine.d.ts +63 -0
- package/dist/analysis/analysis-engine.d.ts.map +1 -0
- package/dist/analysis/analysis-engine.js +322 -0
- package/dist/analysis/analysis-engine.js.map +1 -0
- package/dist/core/config.d.ts +1419 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +361 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/engine.d.ts +176 -0
- package/dist/core/engine.d.ts.map +1 -0
- package/dist/core/engine.js +604 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/errors.d.ts +153 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +287 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/{types.js → core/index.js} +8 -4
- package/dist/core/index.js.map +1 -0
- package/dist/core/pipeline.d.ts +121 -0
- package/dist/core/pipeline.d.ts.map +1 -0
- package/dist/core/pipeline.js +289 -0
- package/dist/core/pipeline.js.map +1 -0
- package/dist/core/rate-limiter.d.ts +58 -0
- package/dist/core/rate-limiter.d.ts.map +1 -0
- package/dist/core/rate-limiter.js +133 -0
- package/dist/core/rate-limiter.js.map +1 -0
- package/dist/core/session-manager.d.ts +96 -0
- package/dist/core/session-manager.d.ts.map +1 -0
- package/dist/core/session-manager.js +223 -0
- package/dist/core/session-manager.js.map +1 -0
- package/dist/creativity/creativity-engine.d.ts +6 -0
- package/dist/creativity/creativity-engine.d.ts.map +1 -0
- package/dist/creativity/creativity-engine.js +17 -0
- package/dist/creativity/creativity-engine.js.map +1 -0
- package/dist/index.d.ts +24 -32
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +130 -104
- package/dist/index.js.map +1 -1
- package/dist/learning/learning-engine.d.ts +6 -0
- package/dist/learning/learning-engine.d.ts.map +1 -0
- package/dist/learning/learning-engine.js +17 -0
- package/dist/learning/learning-engine.js.map +1 -0
- package/dist/llm/index.d.ts +10 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +26 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/llm-service.d.ts +109 -0
- package/dist/llm/llm-service.d.ts.map +1 -0
- package/dist/llm/llm-service.js +224 -0
- package/dist/llm/llm-service.js.map +1 -0
- package/dist/llm/providers/base.d.ts +85 -0
- package/dist/llm/providers/base.d.ts.map +1 -0
- package/dist/llm/providers/base.js +57 -0
- package/dist/llm/providers/base.js.map +1 -0
- package/dist/llm/providers/cli.d.ts +23 -0
- package/dist/llm/providers/cli.d.ts.map +1 -0
- package/dist/llm/providers/cli.js +158 -0
- package/dist/llm/providers/cli.js.map +1 -0
- package/dist/llm/providers/gemini.d.ts +30 -0
- package/dist/llm/providers/gemini.d.ts.map +1 -0
- package/dist/llm/providers/gemini.js +168 -0
- package/dist/llm/providers/gemini.js.map +1 -0
- package/dist/llm/sanitization.d.ts +50 -0
- package/dist/llm/sanitization.d.ts.map +1 -0
- package/dist/llm/sanitization.js +149 -0
- package/dist/llm/sanitization.js.map +1 -0
- package/dist/{server.d.ts.map → mcp/server.d.ts.map} +1 -1
- package/dist/mcp/server.js +108 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/planning/planning-engine.d.ts +6 -0
- package/dist/planning/planning-engine.d.ts.map +1 -0
- package/dist/planning/planning-engine.js +17 -0
- package/dist/planning/planning-engine.js.map +1 -0
- package/dist/reasoning/reasoning-engine.d.ts +6 -0
- package/dist/reasoning/reasoning-engine.d.ts.map +1 -0
- package/dist/reasoning/reasoning-engine.js +17 -0
- package/dist/reasoning/reasoning-engine.js.map +1 -0
- package/dist/search/search-engine.d.ts +99 -0
- package/dist/search/search-engine.d.ts.map +1 -0
- package/dist/search/search-engine.js +271 -0
- package/dist/search/search-engine.js.map +1 -0
- package/dist/synthesis/synthesis-engine.d.ts +6 -0
- package/dist/synthesis/synthesis-engine.d.ts.map +1 -0
- package/dist/synthesis/synthesis-engine.js +17 -0
- package/dist/synthesis/synthesis-engine.js.map +1 -0
- package/dist/types/analysis.d.ts +1534 -49
- package/dist/types/analysis.d.ts.map +1 -1
- package/dist/types/analysis.js +250 -0
- package/dist/types/analysis.js.map +1 -1
- package/dist/types/core.d.ts +257 -30
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/core.js +148 -18
- package/dist/types/core.js.map +1 -1
- package/dist/types/creativity.d.ts +2871 -56
- package/dist/types/creativity.d.ts.map +1 -1
- package/dist/types/creativity.js +195 -0
- package/dist/types/creativity.js.map +1 -1
- package/dist/types/index.d.ts +6 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +17 -2
- package/dist/types/index.js.map +1 -1
- package/dist/types/learning.d.ts +851 -61
- package/dist/types/learning.d.ts.map +1 -1
- package/dist/types/learning.js +155 -0
- package/dist/types/learning.js.map +1 -1
- package/dist/types/planning.d.ts +2223 -71
- package/dist/types/planning.d.ts.map +1 -1
- package/dist/types/planning.js +190 -0
- package/dist/types/planning.js.map +1 -1
- package/dist/types/reasoning.d.ts +2209 -72
- package/dist/types/reasoning.d.ts.map +1 -1
- package/dist/types/reasoning.js +200 -1
- package/dist/types/reasoning.js.map +1 -1
- package/dist/types/search.d.ts +981 -53
- package/dist/types/search.d.ts.map +1 -1
- package/dist/types/search.js +137 -0
- package/dist/types/search.js.map +1 -1
- package/dist/types/synthesis.d.ts +583 -37
- package/dist/types/synthesis.d.ts.map +1 -1
- package/dist/types/synthesis.js +138 -0
- package/dist/types/synthesis.js.map +1 -1
- package/dist/utils/cache.d.ts +144 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +288 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/id-generator.d.ts +89 -0
- package/dist/utils/id-generator.d.ts.map +1 -0
- package/dist/utils/id-generator.js +132 -0
- package/dist/utils/id-generator.js.map +1 -0
- package/dist/utils/index.d.ts +11 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +33 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +142 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +248 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/metrics.d.ts +149 -0
- package/dist/utils/metrics.d.ts.map +1 -0
- package/dist/utils/metrics.js +296 -0
- package/dist/utils/metrics.js.map +1 -0
- package/dist/utils/timer.d.ts +7 -0
- package/dist/utils/timer.d.ts.map +1 -0
- package/dist/utils/timer.js +17 -0
- package/dist/utils/timer.js.map +1 -0
- package/dist/utils/validation.d.ts +147 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +275 -0
- package/dist/utils/validation.js.map +1 -0
- package/docs/API.md +411 -0
- package/docs/ARCHITECTURE.md +271 -0
- package/docs/CHANGELOG.md +283 -0
- package/jest.config.js +28 -0
- package/package.json +43 -30
- package/src/analysis/analysis-engine.ts +383 -0
- package/src/core/config.ts +406 -0
- package/src/core/engine.ts +785 -0
- package/src/core/errors.ts +349 -0
- package/src/core/index.ts +12 -0
- package/src/core/pipeline.ts +424 -0
- package/src/core/rate-limiter.ts +155 -0
- package/src/core/session-manager.ts +269 -0
- package/src/creativity/creativity-engine.ts +14 -0
- package/src/index.ts +178 -0
- package/src/learning/learning-engine.ts +14 -0
- package/src/llm/index.ts +10 -0
- package/src/llm/llm-service.ts +285 -0
- package/src/llm/providers/base.ts +146 -0
- package/src/llm/providers/cli.ts +186 -0
- package/src/llm/providers/gemini.ts +201 -0
- package/src/llm/sanitization.ts +178 -0
- package/src/mcp/server.ts +117 -0
- package/src/planning/planning-engine.ts +14 -0
- package/src/reasoning/reasoning-engine.ts +14 -0
- package/src/search/search-engine.ts +333 -0
- package/src/synthesis/synthesis-engine.ts +14 -0
- package/src/types/analysis.ts +337 -0
- package/src/types/core.ts +342 -0
- package/src/types/creativity.ts +268 -0
- package/src/types/index.ts +31 -0
- package/src/types/learning.ts +215 -0
- package/src/types/planning.ts +251 -0
- package/src/types/reasoning.ts +288 -0
- package/src/types/search.ts +192 -0
- package/src/types/synthesis.ts +187 -0
- package/src/utils/cache.ts +363 -0
- package/src/utils/id-generator.ts +135 -0
- package/src/utils/index.ts +22 -0
- package/src/utils/logger.ts +290 -0
- package/src/utils/metrics.ts +380 -0
- package/src/utils/timer.ts +15 -0
- package/src/utils/validation.ts +297 -0
- package/tests/setup.ts +22 -0
- package/tests/unit/cache.test.ts +189 -0
- package/tests/unit/engine.test.ts +179 -0
- package/tests/unit/validation.test.ts +218 -0
- package/tsconfig.json +17 -12
- package/GEMINI.md +0 -68
- package/analysis.ts +0 -1063
- package/creativity.ts +0 -1055
- package/dist/analysis.d.ts +0 -54
- package/dist/analysis.d.ts.map +0 -1
- package/dist/analysis.js +0 -866
- package/dist/analysis.js.map +0 -1
- package/dist/creativity.d.ts +0 -81
- package/dist/creativity.d.ts.map +0 -1
- package/dist/creativity.js +0 -828
- package/dist/creativity.js.map +0 -1
- package/dist/engine.d.ts +0 -90
- package/dist/engine.d.ts.map +0 -1
- package/dist/engine.js +0 -677
- package/dist/engine.js.map +0 -1
- package/dist/examples.d.ts +0 -7
- package/dist/examples.d.ts.map +0 -1
- package/dist/examples.js +0 -506
- package/dist/examples.js.map +0 -1
- package/dist/learning.d.ts +0 -72
- package/dist/learning.d.ts.map +0 -1
- package/dist/learning.js +0 -615
- package/dist/learning.js.map +0 -1
- package/dist/llm-service.d.ts +0 -21
- package/dist/llm-service.d.ts.map +0 -1
- package/dist/llm-service.js +0 -100
- package/dist/llm-service.js.map +0 -1
- package/dist/planning.d.ts +0 -58
- package/dist/planning.d.ts.map +0 -1
- package/dist/planning.js +0 -824
- package/dist/planning.js.map +0 -1
- package/dist/reasoning.d.ts +0 -73
- package/dist/reasoning.d.ts.map +0 -1
- package/dist/reasoning.js +0 -845
- package/dist/reasoning.js.map +0 -1
- package/dist/search-discovery.d.ts +0 -73
- package/dist/search-discovery.d.ts.map +0 -1
- package/dist/search-discovery.js +0 -548
- package/dist/search-discovery.js.map +0 -1
- package/dist/server.js +0 -113
- package/dist/server.js.map +0 -1
- package/dist/types/engine.d.ts +0 -55
- package/dist/types/engine.d.ts.map +0 -1
- package/dist/types/engine.js +0 -3
- package/dist/types/engine.js.map +0 -1
- package/dist/types.d.ts +0 -6
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/engine.ts +0 -947
- package/examples.ts +0 -717
- package/index.ts +0 -106
- package/learning.ts +0 -779
- package/llm-service.ts +0 -120
- package/planning.ts +0 -1028
- package/reasoning.ts +0 -1079
- package/search-discovery.ts +0 -700
- package/server.ts +0 -115
- package/types/analysis.ts +0 -69
- package/types/core.ts +0 -90
- package/types/creativity.ts +0 -72
- package/types/engine.ts +0 -60
- package/types/index.ts +0 -9
- package/types/learning.ts +0 -69
- package/types/planning.ts +0 -85
- package/types/reasoning.ts +0 -92
- package/types/search.ts +0 -58
- package/types/synthesis.ts +0 -42
- package/types.ts +0 -6
- /package/dist/{server.d.ts → mcp/server.d.ts} +0 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation Utility for gthinking v2.0.0
|
|
3
|
+
* Input sanitization and validation functions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { ValidationError } from '../core/errors';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Sanitize user input to prevent injection attacks
|
|
11
|
+
* @param input - The input string to sanitize
|
|
12
|
+
* @returns Sanitized string
|
|
13
|
+
*/
|
|
14
|
+
export function sanitizeInput(input: string): string {
|
|
15
|
+
if (!input) return '';
|
|
16
|
+
|
|
17
|
+
// Remove potentially dangerous characters
|
|
18
|
+
return input
|
|
19
|
+
.replace(/[<>]/g, '') // Remove HTML tags
|
|
20
|
+
.replace(/[&;|`$(){}[\]\\]/g, '') // Remove shell metacharacters
|
|
21
|
+
.replace(/\0/g, '') // Remove null bytes
|
|
22
|
+
.trim();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Sanitize a string for use in shell commands
|
|
27
|
+
* @param input - The input string to sanitize
|
|
28
|
+
* @returns Shell-safe string
|
|
29
|
+
*/
|
|
30
|
+
export function sanitizeForShell(input: string): string {
|
|
31
|
+
if (!input) return '';
|
|
32
|
+
|
|
33
|
+
// More aggressive sanitization for shell usage
|
|
34
|
+
// Only allow alphanumeric, underscore, hyphen, dot, and space
|
|
35
|
+
return input
|
|
36
|
+
.replace(/[^a-zA-Z0-9_\-\s.]/g, '')
|
|
37
|
+
.trim();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Validate and sanitize an email address
|
|
42
|
+
* @param email - The email to validate
|
|
43
|
+
* @returns Validated email or null if invalid
|
|
44
|
+
*/
|
|
45
|
+
export function validateEmail(email: string): string | null {
|
|
46
|
+
const emailSchema = z.string().email();
|
|
47
|
+
const result = emailSchema.safeParse(email);
|
|
48
|
+
return result.success ? result.data : null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Validate a URL string
|
|
53
|
+
* @param url - The URL to validate
|
|
54
|
+
* @returns True if the URL is valid
|
|
55
|
+
*/
|
|
56
|
+
export function isValidURL(url: string): boolean {
|
|
57
|
+
try {
|
|
58
|
+
new URL(url);
|
|
59
|
+
return true;
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Validate that a string is not empty or whitespace only
|
|
67
|
+
* @param str - The string to validate
|
|
68
|
+
* @returns True if the string has content
|
|
69
|
+
*/
|
|
70
|
+
export function isNonEmptyString(str: unknown): str is string {
|
|
71
|
+
return typeof str === 'string' && str.trim().length > 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Validate that a value is a positive integer
|
|
76
|
+
* @param value - The value to validate
|
|
77
|
+
* @returns True if the value is a positive integer
|
|
78
|
+
*/
|
|
79
|
+
export function isPositiveInteger(value: unknown): value is number {
|
|
80
|
+
return typeof value === 'number' && Number.isInteger(value) && value > 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Validate that a value is within a range
|
|
85
|
+
* @param value - The value to validate
|
|
86
|
+
* @param min - The minimum allowed value
|
|
87
|
+
* @param max - The maximum allowed value
|
|
88
|
+
* @returns True if the value is within the range
|
|
89
|
+
*/
|
|
90
|
+
export function isInRange(value: number, min: number, max: number): boolean {
|
|
91
|
+
return value >= min && value <= max;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Validate an array has unique elements
|
|
96
|
+
* @param arr - The array to validate
|
|
97
|
+
* @returns True if all elements are unique
|
|
98
|
+
*/
|
|
99
|
+
export function hasUniqueElements<T>(arr: T[]): boolean {
|
|
100
|
+
return new Set(arr).size === arr.length;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Safe JSON parse with error handling
|
|
105
|
+
* @param json - The JSON string to parse
|
|
106
|
+
* @returns Parsed object or null if invalid
|
|
107
|
+
*/
|
|
108
|
+
export function safeJsonParse<T>(json: string): T | null {
|
|
109
|
+
try {
|
|
110
|
+
return JSON.parse(json) as T;
|
|
111
|
+
} catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Safe JSON stringify with error handling
|
|
118
|
+
* @param obj - The object to stringify
|
|
119
|
+
* @returns JSON string or null if failed
|
|
120
|
+
*/
|
|
121
|
+
export function safeJsonStringify(obj: unknown): string | null {
|
|
122
|
+
try {
|
|
123
|
+
return JSON.stringify(obj);
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Validate schema with detailed error messages
|
|
131
|
+
* @param schema - The Zod schema to validate against
|
|
132
|
+
* @param data - The data to validate
|
|
133
|
+
* @returns Validated data
|
|
134
|
+
* @throws ValidationError if validation fails
|
|
135
|
+
*/
|
|
136
|
+
export function validateSchema<T>(schema: z.ZodType<T>, data: unknown): T {
|
|
137
|
+
const result = schema.safeParse(data);
|
|
138
|
+
|
|
139
|
+
if (!result.success) {
|
|
140
|
+
const errors = result.error.errors.map(err => ({
|
|
141
|
+
path: err.path.join('.'),
|
|
142
|
+
message: err.message,
|
|
143
|
+
code: err.code,
|
|
144
|
+
}));
|
|
145
|
+
|
|
146
|
+
throw new ValidationError(
|
|
147
|
+
`Validation failed: ${errors.map(e => `${e.path}: ${e.message}`).join(', ')}`,
|
|
148
|
+
{ errors }
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return result.data;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Validate schema asynchronously
|
|
157
|
+
* @param schema - The Zod schema to validate against
|
|
158
|
+
* @param data - The data to validate
|
|
159
|
+
* @returns Promise of validated data
|
|
160
|
+
* @throws ValidationError if validation fails
|
|
161
|
+
*/
|
|
162
|
+
export async function validateSchemaAsync<T>(
|
|
163
|
+
schema: z.ZodType<T>,
|
|
164
|
+
data: unknown
|
|
165
|
+
): Promise<T> {
|
|
166
|
+
const result = await schema.safeParseAsync(data);
|
|
167
|
+
|
|
168
|
+
if (!result.success) {
|
|
169
|
+
const errors = result.error.errors.map(err => ({
|
|
170
|
+
path: err.path.join('.'),
|
|
171
|
+
message: err.message,
|
|
172
|
+
code: err.code,
|
|
173
|
+
}));
|
|
174
|
+
|
|
175
|
+
throw new ValidationError(
|
|
176
|
+
`Validation failed: ${errors.map(e => `${e.path}: ${e.message}`).join(', ')}`,
|
|
177
|
+
{ errors }
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return result.data;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Check if an object has all required properties
|
|
186
|
+
* @param obj - The object to check
|
|
187
|
+
* @param requiredProps - The required property names
|
|
188
|
+
* @returns True if all properties exist
|
|
189
|
+
*/
|
|
190
|
+
export function hasRequiredProperties<T extends object>(
|
|
191
|
+
obj: T,
|
|
192
|
+
requiredProps: (keyof T)[]
|
|
193
|
+
): boolean {
|
|
194
|
+
return requiredProps.every(prop => prop in obj && obj[prop] !== undefined);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Validate that a string length is within bounds
|
|
199
|
+
* @param str - The string to validate
|
|
200
|
+
* @param min - Minimum length (inclusive)
|
|
201
|
+
* @param max - Maximum length (inclusive)
|
|
202
|
+
* @returns True if the string length is valid
|
|
203
|
+
*/
|
|
204
|
+
export function isValidLength(str: string, min: number, max: number): boolean {
|
|
205
|
+
const length = str.length;
|
|
206
|
+
return length >= min && length <= max;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Remove null and undefined values from an object
|
|
211
|
+
* @param obj - The object to clean
|
|
212
|
+
* @returns Object with null/undefined values removed
|
|
213
|
+
*/
|
|
214
|
+
export function removeNullValues<T extends object>(obj: T): Partial<T> {
|
|
215
|
+
return Object.fromEntries(
|
|
216
|
+
Object.entries(obj).filter(([, v]) => v !== null && v !== undefined)
|
|
217
|
+
) as Partial<T>;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Deep clone an object
|
|
222
|
+
* @param obj - The object to clone
|
|
223
|
+
* @returns Deep cloned object
|
|
224
|
+
*/
|
|
225
|
+
export function deepClone<T>(obj: T): T {
|
|
226
|
+
return JSON.parse(JSON.stringify(obj)) as T;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Check if a value is a plain object
|
|
231
|
+
* @param value - The value to check
|
|
232
|
+
* @returns True if the value is a plain object
|
|
233
|
+
*/
|
|
234
|
+
export function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
235
|
+
return typeof value === 'object'
|
|
236
|
+
&& value !== null
|
|
237
|
+
&& !Array.isArray(value)
|
|
238
|
+
&& value.constructor === Object;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Rate limiter for validation attempts
|
|
243
|
+
*/
|
|
244
|
+
export class ValidationRateLimiter {
|
|
245
|
+
private attempts: Map<string, number[]> = new Map();
|
|
246
|
+
private readonly maxAttempts: number;
|
|
247
|
+
private readonly windowMs: number;
|
|
248
|
+
|
|
249
|
+
constructor(maxAttempts = 5, windowMs = 60000) {
|
|
250
|
+
this.maxAttempts = maxAttempts;
|
|
251
|
+
this.windowMs = windowMs;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Check if a key is rate limited
|
|
256
|
+
* @param key - The key to check
|
|
257
|
+
* @returns True if the key is rate limited
|
|
258
|
+
*/
|
|
259
|
+
public isRateLimited(key: string): boolean {
|
|
260
|
+
const now = Date.now();
|
|
261
|
+
const attempts = this.attempts.get(key) || [];
|
|
262
|
+
|
|
263
|
+
// Remove old attempts outside the window
|
|
264
|
+
const validAttempts = attempts.filter(time => now - time < this.windowMs);
|
|
265
|
+
|
|
266
|
+
return validAttempts.length >= this.maxAttempts;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Record an attempt for a key
|
|
271
|
+
* @param key - The key to record
|
|
272
|
+
*/
|
|
273
|
+
public recordAttempt(key: string): void {
|
|
274
|
+
const now = Date.now();
|
|
275
|
+
const attempts = this.attempts.get(key) || [];
|
|
276
|
+
attempts.push(now);
|
|
277
|
+
this.attempts.set(key, attempts);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Reset attempts for a key
|
|
282
|
+
* @param key - The key to reset
|
|
283
|
+
*/
|
|
284
|
+
public reset(key: string): void {
|
|
285
|
+
this.attempts.delete(key);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Clear all attempts
|
|
290
|
+
*/
|
|
291
|
+
public clear(): void {
|
|
292
|
+
this.attempts.clear();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Export a singleton instance
|
|
297
|
+
export const validationRateLimiter = new ValidationRateLimiter();
|
package/tests/setup.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jest Test Setup
|
|
3
|
+
* Configuration and global mocks for testing
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Mock console methods to reduce noise in tests
|
|
7
|
+
global.console = {
|
|
8
|
+
...console,
|
|
9
|
+
log: jest.fn(),
|
|
10
|
+
debug: jest.fn(),
|
|
11
|
+
info: jest.fn(),
|
|
12
|
+
warn: jest.fn(),
|
|
13
|
+
error: console.error,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Set test timeout
|
|
17
|
+
jest.setTimeout(30000);
|
|
18
|
+
|
|
19
|
+
// Clean up after each test
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
jest.clearAllMocks();
|
|
22
|
+
});
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit Tests for Cache
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Cache, CacheManager, cacheManager } from '../../src/utils/cache';
|
|
6
|
+
|
|
7
|
+
describe('Cache', () => {
|
|
8
|
+
let cache: Cache<string>;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
cache = new Cache<string>({ maxSize: 100, defaultTTL: 1000 });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
cache.dispose();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('set and get', () => {
|
|
19
|
+
it('should store and retrieve values', () => {
|
|
20
|
+
cache.set('key1', 'value1');
|
|
21
|
+
expect(cache.get('key1')).toBe('value1');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should return undefined for non-existent keys', () => {
|
|
25
|
+
expect(cache.get('nonexistent')).toBeUndefined();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should return undefined for expired keys', async () => {
|
|
29
|
+
cache.set('key1', 'value1', 10); // 10ms TTL
|
|
30
|
+
expect(cache.get('key1')).toBe('value1');
|
|
31
|
+
|
|
32
|
+
await new Promise(resolve => setTimeout(resolve, 20));
|
|
33
|
+
expect(cache.get('key1')).toBeUndefined();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('has', () => {
|
|
38
|
+
it('should return true for existing keys', () => {
|
|
39
|
+
cache.set('key1', 'value1');
|
|
40
|
+
expect(cache.has('key1')).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should return false for non-existent keys', () => {
|
|
44
|
+
expect(cache.has('nonexistent')).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should return false for expired keys', async () => {
|
|
48
|
+
cache.set('key1', 'value1', 10);
|
|
49
|
+
await new Promise(resolve => setTimeout(resolve, 20));
|
|
50
|
+
expect(cache.has('key1')).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('delete', () => {
|
|
55
|
+
it('should delete existing keys', () => {
|
|
56
|
+
cache.set('key1', 'value1');
|
|
57
|
+
expect(cache.delete('key1')).toBe(true);
|
|
58
|
+
expect(cache.get('key1')).toBeUndefined();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should return false for non-existent keys', () => {
|
|
62
|
+
expect(cache.delete('nonexistent')).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('clear', () => {
|
|
67
|
+
it('should remove all entries', () => {
|
|
68
|
+
cache.set('key1', 'value1');
|
|
69
|
+
cache.set('key2', 'value2');
|
|
70
|
+
|
|
71
|
+
cache.clear();
|
|
72
|
+
|
|
73
|
+
expect(cache.get('key1')).toBeUndefined();
|
|
74
|
+
expect(cache.get('key2')).toBeUndefined();
|
|
75
|
+
expect(cache.size()).toBe(0);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('LRU eviction', () => {
|
|
80
|
+
it('should evict least recently used entries when full', () => {
|
|
81
|
+
const smallCache = new Cache<string>({ maxSize: 2, defaultTTL: 10000 });
|
|
82
|
+
|
|
83
|
+
smallCache.set('key1', 'value1');
|
|
84
|
+
smallCache.set('key2', 'value2');
|
|
85
|
+
smallCache.set('key3', 'value3'); // Should evict key1
|
|
86
|
+
|
|
87
|
+
expect(smallCache.get('key1')).toBeUndefined();
|
|
88
|
+
expect(smallCache.get('key2')).toBe('value2');
|
|
89
|
+
expect(smallCache.get('key3')).toBe('value3');
|
|
90
|
+
|
|
91
|
+
smallCache.dispose();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('statistics', () => {
|
|
96
|
+
it('should track cache statistics', () => {
|
|
97
|
+
cache.set('key1', 'value1');
|
|
98
|
+
cache.get('key1'); // Hit
|
|
99
|
+
cache.get('key2'); // Miss
|
|
100
|
+
|
|
101
|
+
const stats = cache.getStats();
|
|
102
|
+
|
|
103
|
+
expect(stats.size).toBe(1);
|
|
104
|
+
expect(stats.hits).toBe(1);
|
|
105
|
+
expect(stats.misses).toBe(1);
|
|
106
|
+
expect(stats.totalAccesses).toBe(2);
|
|
107
|
+
expect(stats.hitRate).toBe(0.5);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should reset statistics', () => {
|
|
111
|
+
cache.set('key1', 'value1');
|
|
112
|
+
cache.get('key1');
|
|
113
|
+
|
|
114
|
+
cache.resetStats();
|
|
115
|
+
|
|
116
|
+
const stats = cache.getStats();
|
|
117
|
+
expect(stats.hits).toBe(0);
|
|
118
|
+
expect(stats.misses).toBe(0);
|
|
119
|
+
expect(stats.totalAccesses).toBe(0);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('keys and size', () => {
|
|
124
|
+
it('should return all keys', () => {
|
|
125
|
+
cache.set('key1', 'value1');
|
|
126
|
+
cache.set('key2', 'value2');
|
|
127
|
+
|
|
128
|
+
const keys = cache.keys();
|
|
129
|
+
expect(keys).toContain('key1');
|
|
130
|
+
expect(keys).toContain('key2');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should return correct size', () => {
|
|
134
|
+
expect(cache.size()).toBe(0);
|
|
135
|
+
cache.set('key1', 'value1');
|
|
136
|
+
expect(cache.size()).toBe(1);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('CacheManager', () => {
|
|
142
|
+
afterEach(() => {
|
|
143
|
+
cacheManager.dispose();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should create and retrieve caches', () => {
|
|
147
|
+
const cache1 = cacheManager.getCache<string>('cache1');
|
|
148
|
+
const cache2 = cacheManager.getCache<string>('cache2');
|
|
149
|
+
|
|
150
|
+
expect(cache1).toBeDefined();
|
|
151
|
+
expect(cache2).toBeDefined();
|
|
152
|
+
expect(cache1).not.toBe(cache2);
|
|
153
|
+
|
|
154
|
+
// Should return same instance for same name
|
|
155
|
+
const cache1Again = cacheManager.getCache<string>('cache1');
|
|
156
|
+
expect(cache1).toBe(cache1Again);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should delete caches', () => {
|
|
160
|
+
cacheManager.getCache<string>('cache1');
|
|
161
|
+
cacheManager.deleteCache('cache1');
|
|
162
|
+
|
|
163
|
+
// Getting again should create new instance
|
|
164
|
+
const newCache = cacheManager.getCache<string>('cache1');
|
|
165
|
+
expect(newCache).toBeDefined();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should clear all caches', () => {
|
|
169
|
+
const cache1 = cacheManager.getCache<string>('cache1');
|
|
170
|
+
const cache2 = cacheManager.getCache<string>('cache2');
|
|
171
|
+
|
|
172
|
+
cache1.set('key1', 'value1');
|
|
173
|
+
cache2.set('key2', 'value2');
|
|
174
|
+
|
|
175
|
+
cacheManager.clearAll();
|
|
176
|
+
|
|
177
|
+
expect(cache1.get('key1')).toBeUndefined();
|
|
178
|
+
expect(cache2.get('key2')).toBeUndefined();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should return cache names', () => {
|
|
182
|
+
cacheManager.getCache<string>('cache1');
|
|
183
|
+
cacheManager.getCache<string>('cache2');
|
|
184
|
+
|
|
185
|
+
const names = cacheManager.getCacheNames();
|
|
186
|
+
expect(names).toContain('cache1');
|
|
187
|
+
expect(names).toContain('cache2');
|
|
188
|
+
});
|
|
189
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit Tests for SequentialThinkingEngine
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { SequentialThinkingEngine } from '../../src/core/engine';
|
|
6
|
+
import { ThinkingStage } from '../../src/types';
|
|
7
|
+
import { GThinkingError, ValidationError } from '../../src/core/errors';
|
|
8
|
+
|
|
9
|
+
describe('SequentialThinkingEngine', () => {
|
|
10
|
+
let engine: SequentialThinkingEngine;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
engine = new SequentialThinkingEngine();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
engine.dispose();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('constructor', () => {
|
|
21
|
+
it('should create an engine with default config', () => {
|
|
22
|
+
expect(engine).toBeDefined();
|
|
23
|
+
expect(engine.getStats().sessions).toBe(0);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should create an engine with custom config', () => {
|
|
27
|
+
const customEngine = new SequentialThinkingEngine({
|
|
28
|
+
cache: { enabled: false },
|
|
29
|
+
});
|
|
30
|
+
expect(customEngine).toBeDefined();
|
|
31
|
+
customEngine.dispose();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('think', () => {
|
|
36
|
+
it('should validate request schema', async () => {
|
|
37
|
+
const invalidRequest = {
|
|
38
|
+
query: '', // Empty query should fail validation
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
await expect(engine.think(invalidRequest)).rejects.toThrow(ValidationError);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should process a valid request', async () => {
|
|
45
|
+
const request = {
|
|
46
|
+
query: 'Test query',
|
|
47
|
+
preferredStages: [ThinkingStage.SEARCH, ThinkingStage.SYNTHESIS],
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const result = await engine.think(request);
|
|
51
|
+
|
|
52
|
+
expect(result).toBeDefined();
|
|
53
|
+
expect(result.query).toBe(request.query);
|
|
54
|
+
expect(result.success).toBe(true);
|
|
55
|
+
expect(result.confidence).toBeGreaterThan(0);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should emit events during processing', async () => {
|
|
59
|
+
const events: string[] = [];
|
|
60
|
+
|
|
61
|
+
engine.on('thinking:start', () => events.push('start'));
|
|
62
|
+
engine.on('thinking:stage:start', () => events.push('stage:start'));
|
|
63
|
+
engine.on('thinking:stage:complete', () => events.push('stage:complete'));
|
|
64
|
+
engine.on('thinking:complete', () => events.push('complete'));
|
|
65
|
+
|
|
66
|
+
await engine.think({
|
|
67
|
+
query: 'Test query',
|
|
68
|
+
preferredStages: [ThinkingStage.SYNTHESIS],
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(events).toContain('start');
|
|
72
|
+
expect(events).toContain('complete');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should cache results when enabled', async () => {
|
|
76
|
+
const request = {
|
|
77
|
+
query: 'Cached query',
|
|
78
|
+
preferredStages: [ThinkingStage.SYNTHESIS],
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// First call
|
|
82
|
+
const result1 = await engine.think(request);
|
|
83
|
+
|
|
84
|
+
// Second call should use cache
|
|
85
|
+
const result2 = await engine.think(request);
|
|
86
|
+
|
|
87
|
+
expect(result1.query).toBe(result2.query);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('quickSearch', () => {
|
|
92
|
+
it('should execute quick search', async () => {
|
|
93
|
+
const result = await engine.quickSearch('Quick search test');
|
|
94
|
+
|
|
95
|
+
expect(result).toBeDefined();
|
|
96
|
+
expect(result.query).toBe('Quick search test');
|
|
97
|
+
expect(result.success).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('deepAnalysis', () => {
|
|
102
|
+
it('should execute deep analysis', async () => {
|
|
103
|
+
const result = await engine.deepAnalysis('Deep analysis test');
|
|
104
|
+
|
|
105
|
+
expect(result).toBeDefined();
|
|
106
|
+
expect(result.query).toBe('Deep analysis test');
|
|
107
|
+
expect(result.success).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('session management', () => {
|
|
112
|
+
it('should track active sessions', async () => {
|
|
113
|
+
expect(engine.getActiveSessions()).toHaveLength(0);
|
|
114
|
+
|
|
115
|
+
const promise = engine.think({ query: 'Session test' });
|
|
116
|
+
|
|
117
|
+
// Session should be active while processing
|
|
118
|
+
expect(engine.getActiveSessions().length).toBeGreaterThanOrEqual(0);
|
|
119
|
+
|
|
120
|
+
await promise;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should get session by ID', async () => {
|
|
124
|
+
const request = { query: 'Session ID test' };
|
|
125
|
+
|
|
126
|
+
// Start a session
|
|
127
|
+
const thinkPromise = engine.think(request);
|
|
128
|
+
|
|
129
|
+
// Get the session
|
|
130
|
+
const sessions = engine.getActiveSessions();
|
|
131
|
+
if (sessions.length > 0) {
|
|
132
|
+
const session = engine.getSession(sessions[0].id);
|
|
133
|
+
expect(session).toBeDefined();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
await thinkPromise;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should cancel a running session', async () => {
|
|
140
|
+
const request = { query: 'Cancel test' };
|
|
141
|
+
|
|
142
|
+
// Start a session
|
|
143
|
+
const thinkPromise = engine.think(request);
|
|
144
|
+
|
|
145
|
+
// Try to cancel (may or may not work depending on timing)
|
|
146
|
+
const sessions = engine.getActiveSessions();
|
|
147
|
+
if (sessions.length > 0) {
|
|
148
|
+
const cancelled = engine.cancelSession(sessions[0].id);
|
|
149
|
+
expect(typeof cancelled).toBe('boolean');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
await thinkPromise.catch(() => {
|
|
153
|
+
// Cancellation might cause an error, which is fine
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('statistics', () => {
|
|
159
|
+
it('should return engine statistics', () => {
|
|
160
|
+
const stats = engine.getStats();
|
|
161
|
+
|
|
162
|
+
expect(stats).toHaveProperty('sessions');
|
|
163
|
+
expect(stats).toHaveProperty('activeSessions');
|
|
164
|
+
expect(stats).toHaveProperty('cacheStats');
|
|
165
|
+
expect(typeof stats.sessions).toBe('number');
|
|
166
|
+
expect(typeof stats.activeSessions).toBe('number');
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('dispose', () => {
|
|
171
|
+
it('should clean up resources', () => {
|
|
172
|
+
engine.dispose();
|
|
173
|
+
|
|
174
|
+
const stats = engine.getStats();
|
|
175
|
+
expect(stats.sessions).toBe(0);
|
|
176
|
+
expect(stats.activeSessions).toBe(0);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
});
|