gthinking 1.3.0 → 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.
Files changed (271) hide show
  1. package/.eslintrc.js +34 -0
  2. package/ANALYSIS_SUMMARY.md +363 -0
  3. package/README.md +230 -250
  4. package/dist/analysis/analysis-engine.d.ts +63 -0
  5. package/dist/analysis/analysis-engine.d.ts.map +1 -0
  6. package/dist/analysis/analysis-engine.js +322 -0
  7. package/dist/analysis/analysis-engine.js.map +1 -0
  8. package/dist/core/config.d.ts +1419 -0
  9. package/dist/core/config.d.ts.map +1 -0
  10. package/dist/core/config.js +361 -0
  11. package/dist/core/config.js.map +1 -0
  12. package/dist/core/engine.d.ts +176 -0
  13. package/dist/core/engine.d.ts.map +1 -0
  14. package/dist/core/engine.js +604 -0
  15. package/dist/core/engine.js.map +1 -0
  16. package/dist/core/errors.d.ts +153 -0
  17. package/dist/core/errors.d.ts.map +1 -0
  18. package/dist/core/errors.js +287 -0
  19. package/dist/core/errors.js.map +1 -0
  20. package/dist/core/index.d.ts +7 -0
  21. package/dist/core/index.d.ts.map +1 -0
  22. package/dist/{types.js → core/index.js} +8 -4
  23. package/dist/core/index.js.map +1 -0
  24. package/dist/core/pipeline.d.ts +121 -0
  25. package/dist/core/pipeline.d.ts.map +1 -0
  26. package/dist/core/pipeline.js +289 -0
  27. package/dist/core/pipeline.js.map +1 -0
  28. package/dist/core/rate-limiter.d.ts +58 -0
  29. package/dist/core/rate-limiter.d.ts.map +1 -0
  30. package/dist/core/rate-limiter.js +133 -0
  31. package/dist/core/rate-limiter.js.map +1 -0
  32. package/dist/core/session-manager.d.ts +96 -0
  33. package/dist/core/session-manager.d.ts.map +1 -0
  34. package/dist/core/session-manager.js +223 -0
  35. package/dist/core/session-manager.js.map +1 -0
  36. package/dist/creativity/creativity-engine.d.ts +6 -0
  37. package/dist/creativity/creativity-engine.d.ts.map +1 -0
  38. package/dist/creativity/creativity-engine.js +17 -0
  39. package/dist/creativity/creativity-engine.js.map +1 -0
  40. package/dist/index.d.ts +24 -32
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +130 -104
  43. package/dist/index.js.map +1 -1
  44. package/dist/learning/learning-engine.d.ts +6 -0
  45. package/dist/learning/learning-engine.d.ts.map +1 -0
  46. package/dist/learning/learning-engine.js +17 -0
  47. package/dist/learning/learning-engine.js.map +1 -0
  48. package/dist/llm/index.d.ts +10 -0
  49. package/dist/llm/index.d.ts.map +1 -0
  50. package/dist/llm/index.js +26 -0
  51. package/dist/llm/index.js.map +1 -0
  52. package/dist/llm/llm-service.d.ts +109 -0
  53. package/dist/llm/llm-service.d.ts.map +1 -0
  54. package/dist/llm/llm-service.js +224 -0
  55. package/dist/llm/llm-service.js.map +1 -0
  56. package/dist/llm/providers/base.d.ts +85 -0
  57. package/dist/llm/providers/base.d.ts.map +1 -0
  58. package/dist/llm/providers/base.js +57 -0
  59. package/dist/llm/providers/base.js.map +1 -0
  60. package/dist/llm/providers/cli.d.ts +23 -0
  61. package/dist/llm/providers/cli.d.ts.map +1 -0
  62. package/dist/llm/providers/cli.js +158 -0
  63. package/dist/llm/providers/cli.js.map +1 -0
  64. package/dist/llm/providers/gemini.d.ts +30 -0
  65. package/dist/llm/providers/gemini.d.ts.map +1 -0
  66. package/dist/llm/providers/gemini.js +168 -0
  67. package/dist/llm/providers/gemini.js.map +1 -0
  68. package/dist/llm/sanitization.d.ts +50 -0
  69. package/dist/llm/sanitization.d.ts.map +1 -0
  70. package/dist/llm/sanitization.js +149 -0
  71. package/dist/llm/sanitization.js.map +1 -0
  72. package/dist/{server.d.ts.map → mcp/server.d.ts.map} +1 -1
  73. package/dist/mcp/server.js +108 -0
  74. package/dist/mcp/server.js.map +1 -0
  75. package/dist/planning/planning-engine.d.ts +6 -0
  76. package/dist/planning/planning-engine.d.ts.map +1 -0
  77. package/dist/planning/planning-engine.js +17 -0
  78. package/dist/planning/planning-engine.js.map +1 -0
  79. package/dist/reasoning/reasoning-engine.d.ts +6 -0
  80. package/dist/reasoning/reasoning-engine.d.ts.map +1 -0
  81. package/dist/reasoning/reasoning-engine.js +17 -0
  82. package/dist/reasoning/reasoning-engine.js.map +1 -0
  83. package/dist/search/search-engine.d.ts +99 -0
  84. package/dist/search/search-engine.d.ts.map +1 -0
  85. package/dist/search/search-engine.js +271 -0
  86. package/dist/search/search-engine.js.map +1 -0
  87. package/dist/synthesis/synthesis-engine.d.ts +6 -0
  88. package/dist/synthesis/synthesis-engine.d.ts.map +1 -0
  89. package/dist/synthesis/synthesis-engine.js +17 -0
  90. package/dist/synthesis/synthesis-engine.js.map +1 -0
  91. package/dist/types/analysis.d.ts +1534 -49
  92. package/dist/types/analysis.d.ts.map +1 -1
  93. package/dist/types/analysis.js +250 -0
  94. package/dist/types/analysis.js.map +1 -1
  95. package/dist/types/core.d.ts +257 -30
  96. package/dist/types/core.d.ts.map +1 -1
  97. package/dist/types/core.js +148 -18
  98. package/dist/types/core.js.map +1 -1
  99. package/dist/types/creativity.d.ts +2871 -56
  100. package/dist/types/creativity.d.ts.map +1 -1
  101. package/dist/types/creativity.js +195 -0
  102. package/dist/types/creativity.js.map +1 -1
  103. package/dist/types/index.d.ts +6 -2
  104. package/dist/types/index.d.ts.map +1 -1
  105. package/dist/types/index.js +17 -2
  106. package/dist/types/index.js.map +1 -1
  107. package/dist/types/learning.d.ts +851 -61
  108. package/dist/types/learning.d.ts.map +1 -1
  109. package/dist/types/learning.js +155 -0
  110. package/dist/types/learning.js.map +1 -1
  111. package/dist/types/planning.d.ts +2223 -71
  112. package/dist/types/planning.d.ts.map +1 -1
  113. package/dist/types/planning.js +190 -0
  114. package/dist/types/planning.js.map +1 -1
  115. package/dist/types/reasoning.d.ts +2209 -72
  116. package/dist/types/reasoning.d.ts.map +1 -1
  117. package/dist/types/reasoning.js +200 -1
  118. package/dist/types/reasoning.js.map +1 -1
  119. package/dist/types/search.d.ts +981 -53
  120. package/dist/types/search.d.ts.map +1 -1
  121. package/dist/types/search.js +137 -0
  122. package/dist/types/search.js.map +1 -1
  123. package/dist/types/synthesis.d.ts +583 -38
  124. package/dist/types/synthesis.d.ts.map +1 -1
  125. package/dist/types/synthesis.js +138 -0
  126. package/dist/types/synthesis.js.map +1 -1
  127. package/dist/utils/cache.d.ts +144 -0
  128. package/dist/utils/cache.d.ts.map +1 -0
  129. package/dist/utils/cache.js +288 -0
  130. package/dist/utils/cache.js.map +1 -0
  131. package/dist/utils/id-generator.d.ts +89 -0
  132. package/dist/utils/id-generator.d.ts.map +1 -0
  133. package/dist/utils/id-generator.js +132 -0
  134. package/dist/utils/id-generator.js.map +1 -0
  135. package/dist/utils/index.d.ts +11 -0
  136. package/dist/utils/index.d.ts.map +1 -0
  137. package/dist/utils/index.js +33 -0
  138. package/dist/utils/index.js.map +1 -0
  139. package/dist/utils/logger.d.ts +142 -0
  140. package/dist/utils/logger.d.ts.map +1 -0
  141. package/dist/utils/logger.js +248 -0
  142. package/dist/utils/logger.js.map +1 -0
  143. package/dist/utils/metrics.d.ts +149 -0
  144. package/dist/utils/metrics.d.ts.map +1 -0
  145. package/dist/utils/metrics.js +296 -0
  146. package/dist/utils/metrics.js.map +1 -0
  147. package/dist/utils/timer.d.ts +7 -0
  148. package/dist/utils/timer.d.ts.map +1 -0
  149. package/dist/utils/timer.js +17 -0
  150. package/dist/utils/timer.js.map +1 -0
  151. package/dist/utils/validation.d.ts +147 -0
  152. package/dist/utils/validation.d.ts.map +1 -0
  153. package/dist/utils/validation.js +275 -0
  154. package/dist/utils/validation.js.map +1 -0
  155. package/docs/API.md +411 -0
  156. package/docs/ARCHITECTURE.md +271 -0
  157. package/docs/CHANGELOG.md +283 -0
  158. package/jest.config.js +28 -0
  159. package/package.json +43 -30
  160. package/src/analysis/analysis-engine.ts +383 -0
  161. package/src/core/config.ts +406 -0
  162. package/src/core/engine.ts +785 -0
  163. package/src/core/errors.ts +349 -0
  164. package/src/core/index.ts +12 -0
  165. package/src/core/pipeline.ts +424 -0
  166. package/src/core/rate-limiter.ts +155 -0
  167. package/src/core/session-manager.ts +269 -0
  168. package/src/creativity/creativity-engine.ts +14 -0
  169. package/src/index.ts +178 -0
  170. package/src/learning/learning-engine.ts +14 -0
  171. package/src/llm/index.ts +10 -0
  172. package/src/llm/llm-service.ts +285 -0
  173. package/src/llm/providers/base.ts +146 -0
  174. package/src/llm/providers/cli.ts +186 -0
  175. package/src/llm/providers/gemini.ts +201 -0
  176. package/src/llm/sanitization.ts +178 -0
  177. package/src/mcp/server.ts +117 -0
  178. package/src/planning/planning-engine.ts +14 -0
  179. package/src/reasoning/reasoning-engine.ts +14 -0
  180. package/src/search/search-engine.ts +333 -0
  181. package/src/synthesis/synthesis-engine.ts +14 -0
  182. package/src/types/analysis.ts +337 -0
  183. package/src/types/core.ts +342 -0
  184. package/src/types/creativity.ts +268 -0
  185. package/src/types/index.ts +31 -0
  186. package/src/types/learning.ts +215 -0
  187. package/src/types/planning.ts +251 -0
  188. package/src/types/reasoning.ts +288 -0
  189. package/src/types/search.ts +192 -0
  190. package/src/types/synthesis.ts +187 -0
  191. package/src/utils/cache.ts +363 -0
  192. package/src/utils/id-generator.ts +135 -0
  193. package/src/utils/index.ts +22 -0
  194. package/src/utils/logger.ts +290 -0
  195. package/src/utils/metrics.ts +380 -0
  196. package/src/utils/timer.ts +15 -0
  197. package/src/utils/validation.ts +297 -0
  198. package/tests/setup.ts +22 -0
  199. package/tests/unit/cache.test.ts +189 -0
  200. package/tests/unit/engine.test.ts +179 -0
  201. package/tests/unit/validation.test.ts +218 -0
  202. package/tsconfig.json +17 -12
  203. package/GEMINI.md +0 -68
  204. package/analysis.ts +0 -1063
  205. package/creativity.ts +0 -1055
  206. package/dist/analysis.d.ts +0 -54
  207. package/dist/analysis.d.ts.map +0 -1
  208. package/dist/analysis.js +0 -866
  209. package/dist/analysis.js.map +0 -1
  210. package/dist/creativity.d.ts +0 -81
  211. package/dist/creativity.d.ts.map +0 -1
  212. package/dist/creativity.js +0 -828
  213. package/dist/creativity.js.map +0 -1
  214. package/dist/engine.d.ts +0 -90
  215. package/dist/engine.d.ts.map +0 -1
  216. package/dist/engine.js +0 -720
  217. package/dist/engine.js.map +0 -1
  218. package/dist/examples.d.ts +0 -7
  219. package/dist/examples.d.ts.map +0 -1
  220. package/dist/examples.js +0 -506
  221. package/dist/examples.js.map +0 -1
  222. package/dist/learning.d.ts +0 -72
  223. package/dist/learning.d.ts.map +0 -1
  224. package/dist/learning.js +0 -615
  225. package/dist/learning.js.map +0 -1
  226. package/dist/llm-service.d.ts +0 -21
  227. package/dist/llm-service.d.ts.map +0 -1
  228. package/dist/llm-service.js +0 -100
  229. package/dist/llm-service.js.map +0 -1
  230. package/dist/planning.d.ts +0 -62
  231. package/dist/planning.d.ts.map +0 -1
  232. package/dist/planning.js +0 -886
  233. package/dist/planning.js.map +0 -1
  234. package/dist/reasoning.d.ts +0 -73
  235. package/dist/reasoning.d.ts.map +0 -1
  236. package/dist/reasoning.js +0 -845
  237. package/dist/reasoning.js.map +0 -1
  238. package/dist/search-discovery.d.ts +0 -73
  239. package/dist/search-discovery.d.ts.map +0 -1
  240. package/dist/search-discovery.js +0 -548
  241. package/dist/search-discovery.js.map +0 -1
  242. package/dist/server.js +0 -113
  243. package/dist/server.js.map +0 -1
  244. package/dist/types/engine.d.ts +0 -55
  245. package/dist/types/engine.d.ts.map +0 -1
  246. package/dist/types/engine.js +0 -3
  247. package/dist/types/engine.js.map +0 -1
  248. package/dist/types.d.ts +0 -6
  249. package/dist/types.d.ts.map +0 -1
  250. package/dist/types.js.map +0 -1
  251. package/engine.ts +0 -1009
  252. package/examples.ts +0 -717
  253. package/index.ts +0 -106
  254. package/learning.ts +0 -779
  255. package/llm-service.ts +0 -120
  256. package/planning.ts +0 -1101
  257. package/reasoning.ts +0 -1079
  258. package/search-discovery.ts +0 -700
  259. package/server.ts +0 -115
  260. package/types/analysis.ts +0 -69
  261. package/types/core.ts +0 -90
  262. package/types/creativity.ts +0 -72
  263. package/types/engine.ts +0 -60
  264. package/types/index.ts +0 -9
  265. package/types/learning.ts +0 -69
  266. package/types/planning.ts +0 -85
  267. package/types/reasoning.ts +0 -92
  268. package/types/search.ts +0 -58
  269. package/types/synthesis.ts +0 -43
  270. package/types.ts +0 -6
  271. /package/dist/{server.d.ts → mcp/server.d.ts} +0 -0
@@ -0,0 +1,424 @@
1
+ /**
2
+ * Dynamic Pipeline for GThinking
3
+ *
4
+ * Implements a configurable pipeline system that supports parallel
5
+ * execution, dependency management, and error recovery.
6
+ *
7
+ * @module core/pipeline
8
+ */
9
+
10
+ import { EventEmitter } from 'eventemitter3';
11
+ import type {
12
+ ThinkingStage,
13
+ StageResult,
14
+ StreamChunk,
15
+ } from '../types/core';
16
+ import type { PipelineConfig } from './config';
17
+ import { StageError, TimeoutError } from './errors';
18
+ import { logger, createComponentLogger, Timer } from '../utils';
19
+
20
+ /**
21
+ * Stage executor function type
22
+ */
23
+ export type StageExecutor<TInput = unknown, TOutput = unknown> = (
24
+ input: TInput,
25
+ context: PipelineContext
26
+ ) => Promise<TOutput>;
27
+
28
+ /**
29
+ * Pipeline context passed to each stage
30
+ */
31
+ export interface PipelineContext {
32
+ stage: ThinkingStage;
33
+ requestId: string;
34
+ sessionId: string;
35
+ previousResults: Map<ThinkingStage, StageResult>;
36
+ abortSignal: AbortSignal;
37
+ }
38
+
39
+ /**
40
+ * Pipeline stage definition
41
+ */
42
+ export interface PipelineStageDefinition {
43
+ stage: ThinkingStage;
44
+ executor: StageExecutor;
45
+ enabled: boolean;
46
+ parallel: boolean;
47
+ dependencies: ThinkingStage[];
48
+ timeoutMs: number;
49
+ retryCount: number;
50
+ skipOnError: boolean;
51
+ }
52
+
53
+ /**
54
+ * Pipeline events
55
+ */
56
+ export interface PipelineEvents {
57
+ 'stage:start': { stage: ThinkingStage; requestId: string };
58
+ 'stage:complete': { stage: ThinkingStage; result: StageResult; requestId: string };
59
+ 'stage:error': { stage: ThinkingStage; error: Error; requestId: string };
60
+ 'stage:retry': { stage: ThinkingStage; attempt: number; requestId: string };
61
+ 'pipeline:complete': { results: StageResult[]; requestId: string; durationMs: number };
62
+ 'pipeline:error': { error: Error; requestId: string };
63
+ 'chunk': { chunk: StreamChunk; requestId: string };
64
+ }
65
+
66
+ /**
67
+ * Dynamic pipeline implementation
68
+ */
69
+ export class DynamicPipeline extends EventEmitter<PipelineEvents> {
70
+ private readonly stages: Map<ThinkingStage, PipelineStageDefinition> = new Map();
71
+ private readonly config: PipelineConfig;
72
+ private readonly logger = createComponentLogger('Pipeline');
73
+
74
+ constructor(config: PipelineConfig) {
75
+ super();
76
+ this.config = config;
77
+ this.validateConfig();
78
+ }
79
+
80
+ /**
81
+ * Register a stage executor
82
+ */
83
+ registerStage(
84
+ stage: ThinkingStage,
85
+ executor: StageExecutor,
86
+ options: Partial<Omit<PipelineStageDefinition, 'stage' | 'executor'>> = {}
87
+ ): void {
88
+ const stageConfig = (this.config.stages as any)[stage];
89
+
90
+ this.stages.set(stage, {
91
+ stage,
92
+ executor,
93
+ enabled: options.enabled ?? stageConfig?.enabled ?? true,
94
+ parallel: options.parallel ?? stageConfig?.parallel ?? false,
95
+ dependencies: options.dependencies ?? stageConfig?.dependencies ?? [],
96
+ timeoutMs: options.timeoutMs ?? stageConfig?.timeoutMs ?? 30000,
97
+ retryCount: options.retryCount ?? stageConfig?.retryCount ?? 0,
98
+ skipOnError: options.skipOnError ?? stageConfig?.skipOnError ?? false,
99
+ });
100
+
101
+ this.logger.debug('Stage registered', { stage, enabled: this.stages.get(stage)?.enabled });
102
+ }
103
+
104
+ /**
105
+ * Execute the pipeline
106
+ */
107
+ async execute<TInput>(
108
+ input: TInput,
109
+ requestId: string,
110
+ sessionId: string,
111
+ streamResults = false
112
+ ): Promise<StageResult[]> {
113
+ const timer = new Timer();
114
+ const results: Map<ThinkingStage, StageResult> = new Map();
115
+ const abortController = new AbortController();
116
+
117
+ // Set global timeout if configured
118
+ let globalTimeoutId: NodeJS.Timeout | null = null;
119
+ if (this.config.defaultTimeout !== undefined) {
120
+ globalTimeoutId = setTimeout(() => {
121
+ abortController.abort(new TimeoutError('Pipeline global timeout exceeded', this.config.defaultTimeout as any));
122
+ }, this.config.defaultTimeout);
123
+ }
124
+
125
+ try {
126
+ // Get execution order based on dependencies
127
+ const executionOrder = this.getExecutionOrder();
128
+
129
+ for (const stageGroup of executionOrder) {
130
+ // Check if aborted
131
+ if (abortController.signal.aborted) {
132
+ throw abortController.signal.reason;
133
+ }
134
+
135
+ if (stageGroup.length === 1) {
136
+ // Sequential execution
137
+ const stage = stageGroup[0]!;
138
+ const result = await this.executeStage(
139
+ stage,
140
+ input,
141
+ requestId,
142
+ sessionId,
143
+ results,
144
+ abortController.signal,
145
+ streamResults
146
+ );
147
+ results.set(stage, result);
148
+
149
+ // Handle error based on configuration
150
+ if (result.status === 'failed' && !this.stages.get(stage)?.skipOnError) {
151
+ if (this.config.errorHandling === 'stop') {
152
+ throw new StageError(
153
+ `Stage ${stage} failed: ${result.error?.message}`,
154
+ stage,
155
+ false
156
+ );
157
+ }
158
+ }
159
+ } else {
160
+ // Parallel execution
161
+ const parallelResults = await Promise.all(
162
+ stageGroup.map(stage =>
163
+ this.executeStage(
164
+ stage,
165
+ input,
166
+ requestId,
167
+ sessionId,
168
+ results,
169
+ abortController.signal,
170
+ streamResults
171
+ )
172
+ )
173
+ );
174
+
175
+ for (const result of parallelResults) {
176
+ results.set(result.stage, result);
177
+ }
178
+ }
179
+ }
180
+
181
+ const stageResults = Array.from(results.values());
182
+ const durationMs = timer.elapsed();
183
+
184
+ this.emit('pipeline:complete', { results: stageResults, requestId, durationMs });
185
+
186
+ return stageResults;
187
+ } catch (error) {
188
+ this.emit('pipeline:error', { error: error as Error, requestId });
189
+ throw error;
190
+ } finally {
191
+ if (globalTimeoutId !== null) {
192
+ clearTimeout(globalTimeoutId);
193
+ }
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Execute a single stage
199
+ */
200
+ private async executeStage<TInput>(
201
+ stage: ThinkingStage,
202
+ input: TInput,
203
+ requestId: string,
204
+ sessionId: string,
205
+ previousResults: Map<ThinkingStage, StageResult>,
206
+ abortSignal: AbortSignal,
207
+ streamResults: boolean
208
+ ): Promise<StageResult> {
209
+ const stageDef = this.stages.get(stage);
210
+
211
+ if (stageDef === undefined) {
212
+ return {
213
+ stage,
214
+ status: 'skipped',
215
+ timestamp: new Date(),
216
+ data: null,
217
+ };
218
+ }
219
+
220
+ if (!stageDef.enabled) {
221
+ return {
222
+ stage,
223
+ status: 'skipped',
224
+ timestamp: new Date(),
225
+ data: null,
226
+ };
227
+ }
228
+
229
+ // Check dependencies
230
+ for (const dep of stageDef.dependencies) {
231
+ const depResult = previousResults.get(dep);
232
+ if (depResult?.status === 'failed' && !stageDef.skipOnError) {
233
+ return {
234
+ stage,
235
+ status: 'skipped',
236
+ error: new Error(`Dependency stage ${dep} failed`),
237
+ timestamp: new Date(),
238
+ data: null,
239
+ };
240
+ }
241
+ }
242
+
243
+ // Emit stage start
244
+ this.emit('stage:start', { stage, requestId });
245
+ if (streamResults) {
246
+ this.emit('chunk', {
247
+ chunk: { type: 'stage_start', stage, timestamp: Date.now() },
248
+ requestId,
249
+ });
250
+ }
251
+
252
+ const timer = new Timer();
253
+ let lastError: Error | undefined;
254
+
255
+ // Retry loop
256
+ for (let attempt = 0; attempt <= stageDef.retryCount; attempt++) {
257
+ try {
258
+ if (attempt > 0) {
259
+ this.emit('stage:retry', { stage, attempt, requestId });
260
+ }
261
+
262
+ const context: PipelineContext = {
263
+ stage,
264
+ requestId,
265
+ sessionId,
266
+ previousResults,
267
+ abortSignal,
268
+ };
269
+
270
+ // Execute with timeout
271
+ const output = await this.executeWithTimeout(
272
+ () => stageDef.executor(input, context),
273
+ stageDef.timeoutMs
274
+ );
275
+
276
+ const result: StageResult = {
277
+ stage,
278
+ status: 'completed',
279
+ data: output,
280
+ duration: timer.elapsed(),
281
+ timestamp: new Date(),
282
+ };
283
+
284
+ this.emit('stage:complete', { stage, result, requestId });
285
+ if (streamResults) {
286
+ this.emit('chunk', {
287
+ chunk: { type: 'stage_complete', stage, data: output, timestamp: Date.now() },
288
+ requestId,
289
+ });
290
+ }
291
+
292
+ return result;
293
+ } catch (error) {
294
+ lastError = error as Error;
295
+ this.logger.warn(`Stage ${stage} attempt ${attempt + 1} failed`, {
296
+ stage,
297
+ attempt,
298
+ error: lastError.message,
299
+ });
300
+ }
301
+ }
302
+
303
+ // All retries exhausted
304
+ const failedResult: StageResult = {
305
+ stage,
306
+ status: 'failed',
307
+ error: lastError,
308
+ duration: timer.elapsed(),
309
+ timestamp: new Date(),
310
+ data: null,
311
+ };
312
+ this.emit('stage:error', { stage, error: lastError!, requestId });
313
+ if (streamResults) {
314
+ this.emit('chunk', {
315
+ chunk: { type: 'error', stage, data: lastError?.message, timestamp: Date.now() },
316
+ requestId,
317
+ });
318
+ }
319
+
320
+ return failedResult;
321
+ }
322
+
323
+ /**
324
+ * Execute a function with timeout
325
+ */
326
+ private async executeWithTimeout<T>(
327
+ fn: () => Promise<T>,
328
+ timeoutMs: number
329
+ ): Promise<T> {
330
+ return new Promise((resolve, reject) => {
331
+ const timeoutId = setTimeout(() => {
332
+ reject(new TimeoutError(`Operation timed out after ${timeoutMs}ms`, timeoutMs as any));
333
+ }, timeoutMs);
334
+
335
+ fn()
336
+ .then(resolve)
337
+ .catch(reject)
338
+ .finally(() => clearTimeout(timeoutId));
339
+ });
340
+ }
341
+
342
+ /**
343
+ * Get execution order based on dependencies
344
+ */
345
+ private getExecutionOrder(): ThinkingStage[][] {
346
+ const visited = new Set<ThinkingStage>();
347
+ const order: ThinkingStage[][] = [];
348
+ const stages = Array.from(this.stages.entries())
349
+ .filter(([, def]) => def.enabled)
350
+ .map(([stage]) => stage);
351
+
352
+ while (visited.size < stages.length) {
353
+ const group: ThinkingStage[] = [];
354
+
355
+ for (const stage of stages) {
356
+ if (visited.has(stage)) continue;
357
+
358
+ const stageDef = this.stages.get(stage);
359
+ if (stageDef === undefined) continue;
360
+
361
+ // Check if all dependencies are visited
362
+ const depsSatisfied = stageDef.dependencies.every(dep => visited.has(dep));
363
+
364
+ if (depsSatisfied) {
365
+ group.push(stage);
366
+ }
367
+ }
368
+
369
+ if (group.length === 0) {
370
+ // Circular dependency detected
371
+ throw new StageError('Circular dependency detected in pipeline', stages[0]!, false);
372
+ }
373
+
374
+ for (const stage of group) {
375
+ visited.add(stage);
376
+ }
377
+
378
+ order.push(group);
379
+ }
380
+
381
+ return order;
382
+ }
383
+
384
+ /**
385
+ * Validate pipeline configuration
386
+ */
387
+ private validateConfig(): void {
388
+ const stageNames = Object.keys(this.config.stages);
389
+ const uniqueStages = new Set(stageNames);
390
+
391
+ if (stageNames.length !== uniqueStages.size) {
392
+ throw new StageError('Duplicate stages in pipeline configuration', 'search' as any, false);
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Enable a stage
398
+ */
399
+ enableStage(stage: ThinkingStage): void {
400
+ const stageDef = this.stages.get(stage);
401
+ if (stageDef !== undefined) {
402
+ stageDef.enabled = true;
403
+ }
404
+ }
405
+
406
+ /**
407
+ * Disable a stage
408
+ */
409
+ disableStage(stage: ThinkingStage): void {
410
+ const stageDef = this.stages.get(stage);
411
+ if (stageDef !== undefined) {
412
+ stageDef.enabled = false;
413
+ }
414
+ }
415
+
416
+ /**
417
+ * Get enabled stages
418
+ */
419
+ getEnabledStages(): ThinkingStage[] {
420
+ return Array.from(this.stages.entries())
421
+ .filter(([, def]) => def.enabled)
422
+ .map(([stage]) => stage);
423
+ }
424
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Rate Limiter for GThinking
3
+ *
4
+ * Implements token bucket algorithm for rate limiting with
5
+ * support for burst handling and per-session tracking.
6
+ *
7
+ * @module core/rate-limiter
8
+ */
9
+
10
+ import { RateLimiterRes, RateLimiterMemory } from 'rate-limiter-flexible';
11
+ import type { EngineConfig } from '../types/core';
12
+ import { logger } from '../utils/logger';
13
+
14
+ /**
15
+ * Rate limit check result
16
+ */
17
+ export interface RateLimitResult {
18
+ allowed: boolean;
19
+ remainingPoints?: number;
20
+ msBeforeNext?: number;
21
+ retryAfterMs?: number;
22
+ }
23
+
24
+ /**
25
+ * Rate limiter implementation
26
+ */
27
+ export class RateLimiter {
28
+ private readonly limiter: RateLimiterMemory;
29
+ private readonly enabled: boolean;
30
+
31
+ constructor(config: Pick<EngineConfig, 'rateLimitEnabled' | 'rateLimitRequestsPerMinute' | 'rateLimitBurstSize'>) {
32
+ this.enabled = config.rateLimitEnabled;
33
+
34
+ // Convert requests per minute to points per second
35
+ const points = config.rateLimitRequestsPerMinute;
36
+ const duration = 60; // seconds
37
+
38
+ this.limiter = new RateLimiterMemory({
39
+ keyPrefix: 'gthinking',
40
+ points,
41
+ duration,
42
+ });
43
+
44
+ logger.info('Rate limiter initialized', {
45
+ enabled: this.enabled,
46
+ pointsPerMinute: points,
47
+ });
48
+ }
49
+
50
+ /**
51
+ * Check if a request is allowed
52
+ *
53
+ * @param key - Rate limit key (e.g., session ID or IP)
54
+ * @param points - Points to consume (default: 1)
55
+ * @returns Rate limit check result
56
+ */
57
+ async checkLimit(key: string, points = 1): Promise<RateLimitResult> {
58
+ if (!this.enabled) {
59
+ return { allowed: true };
60
+ }
61
+
62
+ try {
63
+ const result = await this.limiter.consume(key, points);
64
+
65
+ return {
66
+ allowed: true,
67
+ remainingPoints: result.remainingPoints,
68
+ msBeforeNext: result.msBeforeNext,
69
+ };
70
+ } catch (rejRes) {
71
+ if (rejRes instanceof RateLimiterRes) {
72
+ return {
73
+ allowed: false,
74
+ msBeforeNext: rejRes.msBeforeNext,
75
+ retryAfterMs: rejRes.msBeforeNext,
76
+ };
77
+ }
78
+
79
+ // Unexpected error, allow request but log
80
+ logger.error('Rate limiter error', { error: String(rejRes) });
81
+ return { allowed: true };
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Get current rate limit status without consuming points
87
+ *
88
+ * @param key - Rate limit key
89
+ * @returns Current status
90
+ */
91
+ async getStatus(key: string): Promise<{ remainingPoints: number; resetTime: Date } | null> {
92
+ if (!this.enabled) {
93
+ return null;
94
+ }
95
+
96
+ try {
97
+ const result = await this.limiter.get(key);
98
+
99
+ if (result === null) {
100
+ return null;
101
+ }
102
+
103
+ return {
104
+ remainingPoints: result.remainingPoints,
105
+ resetTime: new Date(Date.now() + result.msBeforeNext),
106
+ };
107
+ } catch (error) {
108
+ logger.error('Rate limiter getStatus error', { error: String(error) });
109
+ return null;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Reset rate limit for a key
115
+ *
116
+ * @param key - Rate limit key
117
+ */
118
+ async reset(key: string): Promise<void> {
119
+ if (!this.enabled) {
120
+ return;
121
+ }
122
+
123
+ try {
124
+ await this.limiter.delete(key);
125
+ logger.debug('Rate limit reset', { key });
126
+ } catch (error) {
127
+ logger.error('Rate limiter reset error', { error: String(error) });
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Block a key for a specific duration
133
+ *
134
+ * @param key - Rate limit key
135
+ * @param durationMs - Block duration in milliseconds
136
+ */
137
+ async block(key: string, durationMs: number): Promise<void> {
138
+ if (!this.enabled) {
139
+ return;
140
+ }
141
+
142
+ try {
143
+ // Consume all points to block
144
+ const points = await this.limiter.get(key);
145
+ const pointsToConsume = points?.remainingPoints ?? 100;
146
+
147
+ await this.limiter.consume(key, pointsToConsume);
148
+
149
+ // Penalty will last for the specified duration
150
+ logger.info('Key blocked', { key, durationMs });
151
+ } catch (error) {
152
+ logger.error('Rate limiter block error', { error: String(error) });
153
+ }
154
+ }
155
+ }