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,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
|
+
}
|