@vfarcic/dot-ai 0.194.0 → 0.195.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/circuit-breaker.d.ts +159 -0
- package/dist/core/circuit-breaker.d.ts.map +1 -0
- package/dist/core/circuit-breaker.js +299 -0
- package/dist/core/embedding-service.d.ts +6 -0
- package/dist/core/embedding-service.d.ts.map +1 -1
- package/dist/core/embedding-service.js +62 -30
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +6 -1
- package/dist/core/mermaid-tools.d.ts.map +1 -1
- package/dist/core/mermaid-tools.js +1 -3
- package/dist/core/unified-creation-session.d.ts.map +1 -1
- package/dist/core/unified-creation-session.js +7 -0
- package/dist/core/vector-db-service.d.ts +6 -0
- package/dist/core/vector-db-service.d.ts.map +1 -1
- package/dist/core/vector-db-service.js +99 -20
- package/dist/tools/answer-question.d.ts +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Circuit Breaker Implementation
|
|
3
|
+
*
|
|
4
|
+
* Provides resilience for external service calls by preventing cascading failures.
|
|
5
|
+
* Implements the circuit breaker pattern with three states:
|
|
6
|
+
* - CLOSED: Normal operation, requests flow through
|
|
7
|
+
* - OPEN: Failure threshold reached, requests are blocked
|
|
8
|
+
* - HALF_OPEN: Testing recovery, limited requests allowed
|
|
9
|
+
*/
|
|
10
|
+
import { Logger } from './error-handling';
|
|
11
|
+
/**
|
|
12
|
+
* Circuit breaker states
|
|
13
|
+
*/
|
|
14
|
+
export declare enum CircuitState {
|
|
15
|
+
CLOSED = "closed",
|
|
16
|
+
OPEN = "open",
|
|
17
|
+
HALF_OPEN = "half_open"
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Configuration options for circuit breaker
|
|
21
|
+
*/
|
|
22
|
+
export interface CircuitBreakerConfig {
|
|
23
|
+
/** Number of consecutive failures before opening circuit (default: 3) */
|
|
24
|
+
failureThreshold?: number;
|
|
25
|
+
/** Time in milliseconds to wait before transitioning from OPEN to HALF_OPEN (default: 30000) */
|
|
26
|
+
cooldownPeriodMs?: number;
|
|
27
|
+
/** Number of requests to allow in HALF_OPEN state before deciding (default: 1) */
|
|
28
|
+
halfOpenMaxAttempts?: number;
|
|
29
|
+
/** Callback when state changes */
|
|
30
|
+
onStateChange?: (from: CircuitState, to: CircuitState, name: string) => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Statistics about circuit breaker state
|
|
34
|
+
*/
|
|
35
|
+
export interface CircuitBreakerStats {
|
|
36
|
+
state: CircuitState;
|
|
37
|
+
consecutiveFailures: number;
|
|
38
|
+
totalFailures: number;
|
|
39
|
+
totalSuccesses: number;
|
|
40
|
+
lastFailureTime?: Date;
|
|
41
|
+
lastSuccessTime?: Date;
|
|
42
|
+
openedAt?: Date;
|
|
43
|
+
halfOpenAttempts: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Error thrown when circuit is open and blocking requests
|
|
47
|
+
*/
|
|
48
|
+
export declare class CircuitOpenError extends Error {
|
|
49
|
+
readonly circuitName: string;
|
|
50
|
+
readonly remainingCooldownMs: number;
|
|
51
|
+
readonly state: CircuitState;
|
|
52
|
+
constructor(circuitName: string, remainingCooldownMs: number);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Circuit Breaker implementation
|
|
56
|
+
*
|
|
57
|
+
* Usage:
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const breaker = new CircuitBreaker('embedding-api', { failureThreshold: 3 });
|
|
60
|
+
*
|
|
61
|
+
* try {
|
|
62
|
+
* const result = await breaker.execute(() => fetchFromAPI());
|
|
63
|
+
* } catch (error) {
|
|
64
|
+
* if (error instanceof CircuitOpenError) {
|
|
65
|
+
* // Circuit is open, handle gracefully
|
|
66
|
+
* }
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare class CircuitBreaker {
|
|
71
|
+
private readonly name;
|
|
72
|
+
private readonly config;
|
|
73
|
+
private readonly logger;
|
|
74
|
+
private state;
|
|
75
|
+
private consecutiveFailures;
|
|
76
|
+
private totalFailures;
|
|
77
|
+
private totalSuccesses;
|
|
78
|
+
private lastFailureTime?;
|
|
79
|
+
private lastSuccessTime?;
|
|
80
|
+
private openedAt?;
|
|
81
|
+
private halfOpenAttempts;
|
|
82
|
+
constructor(name: string, config?: CircuitBreakerConfig, logger?: Logger);
|
|
83
|
+
/**
|
|
84
|
+
* Execute an operation through the circuit breaker
|
|
85
|
+
* @throws CircuitOpenError if circuit is open
|
|
86
|
+
* @throws Original error if operation fails
|
|
87
|
+
*/
|
|
88
|
+
execute<T>(operation: () => Promise<T>): Promise<T>;
|
|
89
|
+
/**
|
|
90
|
+
* Record a successful operation
|
|
91
|
+
*/
|
|
92
|
+
recordSuccess(): void;
|
|
93
|
+
/**
|
|
94
|
+
* Record a failed operation
|
|
95
|
+
*/
|
|
96
|
+
recordFailure(error?: Error): void;
|
|
97
|
+
/**
|
|
98
|
+
* Reset the circuit breaker to initial state
|
|
99
|
+
*/
|
|
100
|
+
reset(): void;
|
|
101
|
+
/**
|
|
102
|
+
* Get the current circuit state
|
|
103
|
+
*/
|
|
104
|
+
getState(): CircuitState;
|
|
105
|
+
/**
|
|
106
|
+
* Get circuit breaker statistics
|
|
107
|
+
*/
|
|
108
|
+
getStats(): CircuitBreakerStats;
|
|
109
|
+
/**
|
|
110
|
+
* Check if circuit is currently open (blocking requests)
|
|
111
|
+
*/
|
|
112
|
+
isOpen(): boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Get the circuit breaker name
|
|
115
|
+
*/
|
|
116
|
+
getName(): string;
|
|
117
|
+
/**
|
|
118
|
+
* Check if a request can be executed
|
|
119
|
+
*/
|
|
120
|
+
private canExecute;
|
|
121
|
+
/**
|
|
122
|
+
* Check if cooldown period has elapsed
|
|
123
|
+
*/
|
|
124
|
+
private shouldTransitionToHalfOpen;
|
|
125
|
+
/**
|
|
126
|
+
* Get remaining cooldown time in milliseconds
|
|
127
|
+
*/
|
|
128
|
+
private getRemainingCooldown;
|
|
129
|
+
/**
|
|
130
|
+
* Transition to a new state
|
|
131
|
+
*/
|
|
132
|
+
private transitionTo;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Factory for creating circuit breakers with shared configuration
|
|
136
|
+
*/
|
|
137
|
+
export declare class CircuitBreakerFactory {
|
|
138
|
+
private readonly defaultConfig;
|
|
139
|
+
private readonly logger;
|
|
140
|
+
private readonly breakers;
|
|
141
|
+
constructor(defaultConfig?: CircuitBreakerConfig, logger?: Logger);
|
|
142
|
+
/**
|
|
143
|
+
* Get or create a circuit breaker by name
|
|
144
|
+
*/
|
|
145
|
+
getOrCreate(name: string, config?: CircuitBreakerConfig): CircuitBreaker;
|
|
146
|
+
/**
|
|
147
|
+
* Get a circuit breaker by name (returns undefined if not exists)
|
|
148
|
+
*/
|
|
149
|
+
get(name: string): CircuitBreaker | undefined;
|
|
150
|
+
/**
|
|
151
|
+
* Reset all circuit breakers
|
|
152
|
+
*/
|
|
153
|
+
resetAll(): void;
|
|
154
|
+
/**
|
|
155
|
+
* Get stats for all circuit breakers
|
|
156
|
+
*/
|
|
157
|
+
getAllStats(): Record<string, CircuitBreakerStats>;
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=circuit-breaker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"circuit-breaker.d.ts","sourceRoot":"","sources":["../../src/core/circuit-breaker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Circuit Breaker Implementation
|
|
4
|
+
*
|
|
5
|
+
* Provides resilience for external service calls by preventing cascading failures.
|
|
6
|
+
* Implements the circuit breaker pattern with three states:
|
|
7
|
+
* - CLOSED: Normal operation, requests flow through
|
|
8
|
+
* - OPEN: Failure threshold reached, requests are blocked
|
|
9
|
+
* - HALF_OPEN: Testing recovery, limited requests allowed
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.CircuitBreakerFactory = exports.CircuitBreaker = exports.CircuitOpenError = exports.CircuitState = void 0;
|
|
13
|
+
const error_handling_1 = require("./error-handling");
|
|
14
|
+
/**
|
|
15
|
+
* Circuit breaker states
|
|
16
|
+
*/
|
|
17
|
+
var CircuitState;
|
|
18
|
+
(function (CircuitState) {
|
|
19
|
+
CircuitState["CLOSED"] = "closed";
|
|
20
|
+
CircuitState["OPEN"] = "open";
|
|
21
|
+
CircuitState["HALF_OPEN"] = "half_open";
|
|
22
|
+
})(CircuitState || (exports.CircuitState = CircuitState = {}));
|
|
23
|
+
/**
|
|
24
|
+
* Error thrown when circuit is open and blocking requests
|
|
25
|
+
*/
|
|
26
|
+
class CircuitOpenError extends Error {
|
|
27
|
+
circuitName;
|
|
28
|
+
remainingCooldownMs;
|
|
29
|
+
state;
|
|
30
|
+
constructor(circuitName, remainingCooldownMs) {
|
|
31
|
+
super(`Circuit '${circuitName}' is open. Retry after ${Math.ceil(remainingCooldownMs)}ms`);
|
|
32
|
+
this.name = 'CircuitOpenError';
|
|
33
|
+
this.circuitName = circuitName;
|
|
34
|
+
this.remainingCooldownMs = remainingCooldownMs;
|
|
35
|
+
this.state = CircuitState.OPEN;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.CircuitOpenError = CircuitOpenError;
|
|
39
|
+
/**
|
|
40
|
+
* Circuit Breaker implementation
|
|
41
|
+
*
|
|
42
|
+
* Usage:
|
|
43
|
+
* ```typescript
|
|
44
|
+
* const breaker = new CircuitBreaker('embedding-api', { failureThreshold: 3 });
|
|
45
|
+
*
|
|
46
|
+
* try {
|
|
47
|
+
* const result = await breaker.execute(() => fetchFromAPI());
|
|
48
|
+
* } catch (error) {
|
|
49
|
+
* if (error instanceof CircuitOpenError) {
|
|
50
|
+
* // Circuit is open, handle gracefully
|
|
51
|
+
* }
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
class CircuitBreaker {
|
|
56
|
+
name;
|
|
57
|
+
config;
|
|
58
|
+
logger;
|
|
59
|
+
state = CircuitState.CLOSED;
|
|
60
|
+
consecutiveFailures = 0;
|
|
61
|
+
totalFailures = 0;
|
|
62
|
+
totalSuccesses = 0;
|
|
63
|
+
lastFailureTime;
|
|
64
|
+
lastSuccessTime;
|
|
65
|
+
openedAt;
|
|
66
|
+
halfOpenAttempts = 0;
|
|
67
|
+
constructor(name, config, logger) {
|
|
68
|
+
this.name = name;
|
|
69
|
+
this.config = {
|
|
70
|
+
failureThreshold: config?.failureThreshold ?? 3,
|
|
71
|
+
cooldownPeriodMs: config?.cooldownPeriodMs ?? 30000,
|
|
72
|
+
halfOpenMaxAttempts: config?.halfOpenMaxAttempts ?? 1,
|
|
73
|
+
onStateChange: config?.onStateChange
|
|
74
|
+
};
|
|
75
|
+
this.logger = logger ?? new error_handling_1.ConsoleLogger('CircuitBreaker');
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Execute an operation through the circuit breaker
|
|
79
|
+
* @throws CircuitOpenError if circuit is open
|
|
80
|
+
* @throws Original error if operation fails
|
|
81
|
+
*/
|
|
82
|
+
async execute(operation) {
|
|
83
|
+
// Check if we should allow the request
|
|
84
|
+
if (!this.canExecute()) {
|
|
85
|
+
const remainingCooldown = this.getRemainingCooldown();
|
|
86
|
+
this.logger.warn(`Circuit '${this.name}' is open, blocking request`, {
|
|
87
|
+
remainingCooldownMs: remainingCooldown
|
|
88
|
+
});
|
|
89
|
+
throw new CircuitOpenError(this.name, remainingCooldown);
|
|
90
|
+
}
|
|
91
|
+
// Track half-open attempts
|
|
92
|
+
if (this.state === CircuitState.HALF_OPEN) {
|
|
93
|
+
this.halfOpenAttempts++;
|
|
94
|
+
this.logger.info(`Circuit '${this.name}' attempting request in half-open state`, {
|
|
95
|
+
attempt: this.halfOpenAttempts,
|
|
96
|
+
maxAttempts: this.config.halfOpenMaxAttempts
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const result = await operation();
|
|
101
|
+
this.recordSuccess();
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
this.recordFailure(error);
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Record a successful operation
|
|
111
|
+
*/
|
|
112
|
+
recordSuccess() {
|
|
113
|
+
this.consecutiveFailures = 0;
|
|
114
|
+
this.totalSuccesses++;
|
|
115
|
+
this.lastSuccessTime = new Date();
|
|
116
|
+
if (this.state === CircuitState.HALF_OPEN) {
|
|
117
|
+
// Success in half-open state closes the circuit
|
|
118
|
+
this.transitionTo(CircuitState.CLOSED);
|
|
119
|
+
this.halfOpenAttempts = 0;
|
|
120
|
+
this.logger.info(`Circuit '${this.name}' recovered, transitioning to closed`, {
|
|
121
|
+
totalSuccesses: this.totalSuccesses
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Record a failed operation
|
|
127
|
+
*/
|
|
128
|
+
recordFailure(error) {
|
|
129
|
+
this.consecutiveFailures++;
|
|
130
|
+
this.totalFailures++;
|
|
131
|
+
this.lastFailureTime = new Date();
|
|
132
|
+
this.logger.warn(`Circuit '${this.name}' recorded failure`, {
|
|
133
|
+
consecutiveFailures: this.consecutiveFailures,
|
|
134
|
+
threshold: this.config.failureThreshold,
|
|
135
|
+
error: error?.message
|
|
136
|
+
});
|
|
137
|
+
if (this.state === CircuitState.HALF_OPEN) {
|
|
138
|
+
// Failure in half-open state opens the circuit again
|
|
139
|
+
this.transitionTo(CircuitState.OPEN);
|
|
140
|
+
this.openedAt = new Date();
|
|
141
|
+
this.halfOpenAttempts = 0;
|
|
142
|
+
this.logger.warn(`Circuit '${this.name}' failed in half-open state, reopening`);
|
|
143
|
+
}
|
|
144
|
+
else if (this.state === CircuitState.CLOSED && this.consecutiveFailures >= this.config.failureThreshold) {
|
|
145
|
+
// Threshold reached, open the circuit
|
|
146
|
+
this.transitionTo(CircuitState.OPEN);
|
|
147
|
+
this.openedAt = new Date();
|
|
148
|
+
this.logger.error(`Circuit '${this.name}' opened after ${this.consecutiveFailures} consecutive failures`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Reset the circuit breaker to initial state
|
|
153
|
+
*/
|
|
154
|
+
reset() {
|
|
155
|
+
const previousState = this.state;
|
|
156
|
+
this.state = CircuitState.CLOSED;
|
|
157
|
+
this.consecutiveFailures = 0;
|
|
158
|
+
this.halfOpenAttempts = 0;
|
|
159
|
+
this.openedAt = undefined;
|
|
160
|
+
if (previousState !== CircuitState.CLOSED) {
|
|
161
|
+
this.logger.info(`Circuit '${this.name}' manually reset to closed`);
|
|
162
|
+
this.config.onStateChange?.(previousState, CircuitState.CLOSED, this.name);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get the current circuit state
|
|
167
|
+
*/
|
|
168
|
+
getState() {
|
|
169
|
+
// Check if we should transition from OPEN to HALF_OPEN
|
|
170
|
+
if (this.state === CircuitState.OPEN && this.shouldTransitionToHalfOpen()) {
|
|
171
|
+
this.transitionTo(CircuitState.HALF_OPEN);
|
|
172
|
+
this.halfOpenAttempts = 0;
|
|
173
|
+
this.logger.info(`Circuit '${this.name}' cooldown elapsed, transitioning to half-open`);
|
|
174
|
+
}
|
|
175
|
+
return this.state;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Get circuit breaker statistics
|
|
179
|
+
*/
|
|
180
|
+
getStats() {
|
|
181
|
+
return {
|
|
182
|
+
state: this.getState(),
|
|
183
|
+
consecutiveFailures: this.consecutiveFailures,
|
|
184
|
+
totalFailures: this.totalFailures,
|
|
185
|
+
totalSuccesses: this.totalSuccesses,
|
|
186
|
+
lastFailureTime: this.lastFailureTime,
|
|
187
|
+
lastSuccessTime: this.lastSuccessTime,
|
|
188
|
+
openedAt: this.openedAt,
|
|
189
|
+
halfOpenAttempts: this.halfOpenAttempts
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Check if circuit is currently open (blocking requests)
|
|
194
|
+
*/
|
|
195
|
+
isOpen() {
|
|
196
|
+
return this.getState() === CircuitState.OPEN;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Get the circuit breaker name
|
|
200
|
+
*/
|
|
201
|
+
getName() {
|
|
202
|
+
return this.name;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Check if a request can be executed
|
|
206
|
+
*/
|
|
207
|
+
canExecute() {
|
|
208
|
+
const currentState = this.getState();
|
|
209
|
+
switch (currentState) {
|
|
210
|
+
case CircuitState.CLOSED:
|
|
211
|
+
return true;
|
|
212
|
+
case CircuitState.OPEN:
|
|
213
|
+
return false;
|
|
214
|
+
case CircuitState.HALF_OPEN:
|
|
215
|
+
// Allow limited requests in half-open state
|
|
216
|
+
return this.halfOpenAttempts < this.config.halfOpenMaxAttempts;
|
|
217
|
+
default:
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Check if cooldown period has elapsed
|
|
223
|
+
*/
|
|
224
|
+
shouldTransitionToHalfOpen() {
|
|
225
|
+
if (!this.openedAt) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
const elapsed = Date.now() - this.openedAt.getTime();
|
|
229
|
+
return elapsed >= this.config.cooldownPeriodMs;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Get remaining cooldown time in milliseconds
|
|
233
|
+
*/
|
|
234
|
+
getRemainingCooldown() {
|
|
235
|
+
if (!this.openedAt) {
|
|
236
|
+
return 0;
|
|
237
|
+
}
|
|
238
|
+
const elapsed = Date.now() - this.openedAt.getTime();
|
|
239
|
+
const remaining = this.config.cooldownPeriodMs - elapsed;
|
|
240
|
+
return Math.max(0, remaining);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Transition to a new state
|
|
244
|
+
*/
|
|
245
|
+
transitionTo(newState) {
|
|
246
|
+
const previousState = this.state;
|
|
247
|
+
this.state = newState;
|
|
248
|
+
this.config.onStateChange?.(previousState, newState, this.name);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
exports.CircuitBreaker = CircuitBreaker;
|
|
252
|
+
/**
|
|
253
|
+
* Factory for creating circuit breakers with shared configuration
|
|
254
|
+
*/
|
|
255
|
+
class CircuitBreakerFactory {
|
|
256
|
+
defaultConfig;
|
|
257
|
+
logger;
|
|
258
|
+
breakers = new Map();
|
|
259
|
+
constructor(defaultConfig, logger) {
|
|
260
|
+
this.defaultConfig = defaultConfig ?? {};
|
|
261
|
+
this.logger = logger ?? new error_handling_1.ConsoleLogger('CircuitBreakerFactory');
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Get or create a circuit breaker by name
|
|
265
|
+
*/
|
|
266
|
+
getOrCreate(name, config) {
|
|
267
|
+
let breaker = this.breakers.get(name);
|
|
268
|
+
if (!breaker) {
|
|
269
|
+
breaker = new CircuitBreaker(name, { ...this.defaultConfig, ...config }, this.logger);
|
|
270
|
+
this.breakers.set(name, breaker);
|
|
271
|
+
}
|
|
272
|
+
return breaker;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Get a circuit breaker by name (returns undefined if not exists)
|
|
276
|
+
*/
|
|
277
|
+
get(name) {
|
|
278
|
+
return this.breakers.get(name);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Reset all circuit breakers
|
|
282
|
+
*/
|
|
283
|
+
resetAll() {
|
|
284
|
+
for (const breaker of this.breakers.values()) {
|
|
285
|
+
breaker.reset();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Get stats for all circuit breakers
|
|
290
|
+
*/
|
|
291
|
+
getAllStats() {
|
|
292
|
+
const stats = {};
|
|
293
|
+
for (const [name, breaker] of this.breakers) {
|
|
294
|
+
stats[name] = breaker.getStats();
|
|
295
|
+
}
|
|
296
|
+
return stats;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
exports.CircuitBreakerFactory = CircuitBreakerFactory;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Optional semantic search enhancement for pattern matching.
|
|
5
5
|
* Gracefully falls back to keyword-only search when embedding providers are not available.
|
|
6
6
|
*/
|
|
7
|
+
import { CircuitBreakerStats } from './circuit-breaker';
|
|
7
8
|
/**
|
|
8
9
|
* Supported embedding providers - single source of truth
|
|
9
10
|
*/
|
|
@@ -87,5 +88,10 @@ export declare class EmbeddingService {
|
|
|
87
88
|
suggestedResources: string[];
|
|
88
89
|
rationale: string;
|
|
89
90
|
}): string;
|
|
91
|
+
/**
|
|
92
|
+
* Get circuit breaker statistics for monitoring
|
|
93
|
+
* Returns null if embedding service is not available
|
|
94
|
+
*/
|
|
95
|
+
getCircuitBreakerStats(): CircuitBreakerStats | null;
|
|
90
96
|
}
|
|
91
97
|
//# sourceMappingURL=embedding-service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embedding-service.d.ts","sourceRoot":"","sources":["../../src/core/embedding-service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"embedding-service.d.ts","sourceRoot":"","sources":["../../src/core/embedding-service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAAkB,mBAAmB,EAAoB,MAAM,mBAAmB,CAAC;AAc1F;;GAEG;AACH,eAAO,MAAM,mBAAmB,iDAAkD,CAAC;AACnF,MAAM,MAAM,qBAAqB,GAAG,OAAO,mBAAmB,CAAC,MAAM,CAAC,CAAC;AAEvE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACnD,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACzD,WAAW,IAAI,OAAO,CAAC;IACvB,aAAa,IAAI,MAAM,CAAC;IACxB,QAAQ,IAAI,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,qBAAa,uBAAwB,YAAW,iBAAiB;IAC/D,OAAO,CAAC,YAAY,CAAwB;IAC5C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,aAAa,CAAM;gBAEf,MAAM,EAAE,eAAe,GAAG;QAAE,QAAQ,EAAE,qBAAqB,CAAA;KAAE;IAgEnE,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAwDlD,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;IAqE9D,WAAW,IAAI,OAAO;IAItB,aAAa,IAAI,MAAM;IAIvB,QAAQ,IAAI,MAAM;IAIlB,eAAe,IAAI,MAAM;CAG1B;AA0BD;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAA2B;gBAE/B,MAAM,GAAE,eAAoB;IAKxC;;;OAGG;IACG,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAaxD;;;OAGG;IACG,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;IAc9D;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,aAAa,IAAI,MAAM;IAIvB;;OAEG;IACH,SAAS,IAAI;QACX,SAAS,EAAE,OAAO,CAAC;QACnB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB;IA4BD;;OAEG;IACH,uBAAuB,CAAC,OAAO,EAAE;QAC/B,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,kBAAkB,EAAE,MAAM,EAAE,CAAC;QAC7B,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,MAAM;IAUV;;;OAGG;IACH,sBAAsB,IAAI,mBAAmB,GAAG,IAAI;CAGrD"}
|
|
@@ -11,7 +11,18 @@ const amazon_bedrock_1 = require("@ai-sdk/amazon-bedrock");
|
|
|
11
11
|
const google_1 = require("@ai-sdk/google");
|
|
12
12
|
const openai_1 = require("@ai-sdk/openai");
|
|
13
13
|
const ai_1 = require("ai");
|
|
14
|
+
const circuit_breaker_1 = require("./circuit-breaker");
|
|
14
15
|
const tracing_1 = require("./tracing");
|
|
16
|
+
/**
|
|
17
|
+
* Module-level circuit breaker for embedding API calls.
|
|
18
|
+
* Shared across all EmbeddingService instances since they hit the same API.
|
|
19
|
+
* Opens after 3 consecutive failures, blocks for 30s before testing recovery.
|
|
20
|
+
*/
|
|
21
|
+
const embeddingCircuitBreaker = new circuit_breaker_1.CircuitBreaker('embedding-api', {
|
|
22
|
+
failureThreshold: 3, // Open after 3 consecutive failures
|
|
23
|
+
cooldownPeriodMs: 30000, // 30s cooldown before half-open
|
|
24
|
+
halfOpenMaxAttempts: 1 // Allow 1 test request in half-open
|
|
25
|
+
});
|
|
15
26
|
/**
|
|
16
27
|
* Supported embedding providers - single source of truth
|
|
17
28
|
*/
|
|
@@ -102,23 +113,30 @@ class VercelEmbeddingProvider {
|
|
|
102
113
|
operation: 'embeddings'
|
|
103
114
|
}, async () => {
|
|
104
115
|
try {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (this.providerType === 'google') {
|
|
111
|
-
embedOptions.providerOptions = {
|
|
112
|
-
google: {
|
|
113
|
-
outputDimensionality: this.dimensions,
|
|
114
|
-
taskType: 'SEMANTIC_SIMILARITY'
|
|
115
|
-
}
|
|
116
|
+
// Execute through circuit breaker to prevent cascading failures
|
|
117
|
+
return await embeddingCircuitBreaker.execute(async () => {
|
|
118
|
+
const embedOptions = {
|
|
119
|
+
model: this.modelInstance,
|
|
120
|
+
value: text.trim()
|
|
116
121
|
};
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
122
|
+
// Add Google-specific options
|
|
123
|
+
if (this.providerType === 'google') {
|
|
124
|
+
embedOptions.providerOptions = {
|
|
125
|
+
google: {
|
|
126
|
+
outputDimensionality: this.dimensions,
|
|
127
|
+
taskType: 'SEMANTIC_SIMILARITY'
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const result = await (0, ai_1.embed)(embedOptions);
|
|
132
|
+
return result.embedding;
|
|
133
|
+
});
|
|
120
134
|
}
|
|
121
135
|
catch (error) {
|
|
136
|
+
// Convert CircuitOpenError to descriptive message
|
|
137
|
+
if (error instanceof circuit_breaker_1.CircuitOpenError) {
|
|
138
|
+
throw new Error(`Embedding API circuit open: ${error.message}`);
|
|
139
|
+
}
|
|
122
140
|
if (error instanceof Error) {
|
|
123
141
|
throw new Error(`${this.providerType} embedding failed: ${error.message}`);
|
|
124
142
|
}
|
|
@@ -149,25 +167,32 @@ class VercelEmbeddingProvider {
|
|
|
149
167
|
operation: 'embeddings'
|
|
150
168
|
}, async () => {
|
|
151
169
|
try {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (this.providerType === 'google') {
|
|
159
|
-
embedOptions.providerOptions = {
|
|
160
|
-
google: {
|
|
161
|
-
outputDimensionality: this.dimensions,
|
|
162
|
-
taskType: 'SEMANTIC_SIMILARITY'
|
|
163
|
-
}
|
|
170
|
+
// Execute through circuit breaker to prevent cascading failures
|
|
171
|
+
return await embeddingCircuitBreaker.execute(async () => {
|
|
172
|
+
const results = await Promise.all(validTexts.map(text => {
|
|
173
|
+
const embedOptions = {
|
|
174
|
+
model: this.modelInstance,
|
|
175
|
+
value: text
|
|
164
176
|
};
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
177
|
+
// Add Google-specific options
|
|
178
|
+
if (this.providerType === 'google') {
|
|
179
|
+
embedOptions.providerOptions = {
|
|
180
|
+
google: {
|
|
181
|
+
outputDimensionality: this.dimensions,
|
|
182
|
+
taskType: 'SEMANTIC_SIMILARITY'
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
return (0, ai_1.embed)(embedOptions);
|
|
187
|
+
}));
|
|
188
|
+
return results.map(result => result.embedding);
|
|
189
|
+
});
|
|
169
190
|
}
|
|
170
191
|
catch (error) {
|
|
192
|
+
// Convert CircuitOpenError to descriptive message
|
|
193
|
+
if (error instanceof circuit_breaker_1.CircuitOpenError) {
|
|
194
|
+
throw new Error(`Embedding API circuit open: ${error.message}`);
|
|
195
|
+
}
|
|
171
196
|
if (error instanceof Error) {
|
|
172
197
|
throw new Error(`${this.providerType} batch embedding failed: ${error.message}`);
|
|
173
198
|
}
|
|
@@ -308,5 +333,12 @@ class EmbeddingService {
|
|
|
308
333
|
...pattern.suggestedResources.map(r => `kubernetes ${r.toLowerCase()}`)
|
|
309
334
|
].join(' ').trim();
|
|
310
335
|
}
|
|
336
|
+
/**
|
|
337
|
+
* Get circuit breaker statistics for monitoring
|
|
338
|
+
* Returns null if embedding service is not available
|
|
339
|
+
*/
|
|
340
|
+
getCircuitBreakerStats() {
|
|
341
|
+
return embeddingCircuitBreaker.getStats();
|
|
342
|
+
}
|
|
311
343
|
}
|
|
312
344
|
exports.EmbeddingService = EmbeddingService;
|
package/dist/core/index.d.ts
CHANGED
|
@@ -53,5 +53,6 @@ export { PolicyVectorService, PolicySearchOptions, PolicySearchResult } from './
|
|
|
53
53
|
export { CapabilityVectorService, ResourceCapability, CapabilitySearchOptions } from './capability-vector-service';
|
|
54
54
|
export { EmbeddingService, EmbeddingConfig, EmbeddingProvider, VercelEmbeddingProvider } from './embedding-service';
|
|
55
55
|
export { AgentDisplayOptions, buildAgentDisplayBlock } from './agent-display';
|
|
56
|
+
export { CircuitBreaker, CircuitBreakerFactory, CircuitBreakerConfig, CircuitBreakerStats, CircuitState, CircuitOpenError } from './circuit-breaker';
|
|
56
57
|
export default DotAI;
|
|
57
58
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/core/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC/F,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG7C,MAAM,WAAW,UAAU;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,KAAK;IAChB,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,WAAW,CAAkB;IAErC,SAAgB,SAAS,EAAE,mBAAmB,CAAC;IAC/C,SAAgB,MAAM,EAAE,YAAY,CAAC;IACrC,SAAgB,QAAQ,EAAE,cAAc,CAAC;IACzC,SAAgB,EAAE,EAAE,UAAU,CAAC;IAC/B,SAAgB,MAAM,EAAE;QACtB,MAAM,EAAE,YAAY,CAAC;QACrB,SAAS,EAAE,iBAAiB,CAAC;QAC7B,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAAC;QACnC,aAAa,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;QACtD,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;QAChD,6BAA6B,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;QAC9I,qBAAqB,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC;YAAE,UAAU,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KAClG,CAAC;gBAEU,MAAM,GAAE,UAAe;IA2E7B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAc3B,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IAa/C,aAAa,IAAI,OAAO;IAIxB,UAAU,IAAI,MAAM;CAGrB;AAGD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACnF,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAChF,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,wBAAwB,EAAE,YAAY,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACpI,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE5G,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC/F,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC3G,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACvG,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACnH,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AACpH,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"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"}
|
package/dist/core/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Shared intelligence for both CLI and MCP interfaces
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.buildAgentDisplayBlock = exports.VercelEmbeddingProvider = exports.EmbeddingService = exports.CapabilityVectorService = exports.PolicyVectorService = exports.PatternVectorService = exports.BaseVectorService = exports.VectorDBService = exports.deserializePattern = exports.serializePattern = exports.createPattern = exports.validatePattern = exports.ResourceRecommender = exports.ManifestValidator = exports.SchemaParser = exports.AIProviderFactory = exports.createAIProvider = exports.WorkflowEngine = exports.MemorySystem = exports.KubernetesDiscovery = exports.DotAI = void 0;
|
|
8
|
+
exports.CircuitOpenError = exports.CircuitState = exports.CircuitBreakerFactory = exports.CircuitBreaker = exports.buildAgentDisplayBlock = exports.VercelEmbeddingProvider = exports.EmbeddingService = exports.CapabilityVectorService = exports.PolicyVectorService = exports.PatternVectorService = exports.BaseVectorService = exports.VectorDBService = exports.deserializePattern = exports.serializePattern = exports.createPattern = exports.validatePattern = exports.ResourceRecommender = exports.ManifestValidator = exports.SchemaParser = exports.AIProviderFactory = exports.createAIProvider = exports.WorkflowEngine = exports.MemorySystem = exports.KubernetesDiscovery = exports.DotAI = void 0;
|
|
9
9
|
const discovery_1 = require("./discovery");
|
|
10
10
|
const memory_1 = require("./memory");
|
|
11
11
|
const workflow_1 = require("./workflow");
|
|
@@ -153,5 +153,10 @@ Object.defineProperty(exports, "EmbeddingService", { enumerable: true, get: func
|
|
|
153
153
|
Object.defineProperty(exports, "VercelEmbeddingProvider", { enumerable: true, get: function () { return embedding_service_1.VercelEmbeddingProvider; } });
|
|
154
154
|
var agent_display_1 = require("./agent-display");
|
|
155
155
|
Object.defineProperty(exports, "buildAgentDisplayBlock", { enumerable: true, get: function () { return agent_display_1.buildAgentDisplayBlock; } });
|
|
156
|
+
var circuit_breaker_1 = require("./circuit-breaker");
|
|
157
|
+
Object.defineProperty(exports, "CircuitBreaker", { enumerable: true, get: function () { return circuit_breaker_1.CircuitBreaker; } });
|
|
158
|
+
Object.defineProperty(exports, "CircuitBreakerFactory", { enumerable: true, get: function () { return circuit_breaker_1.CircuitBreakerFactory; } });
|
|
159
|
+
Object.defineProperty(exports, "CircuitState", { enumerable: true, get: function () { return circuit_breaker_1.CircuitState; } });
|
|
160
|
+
Object.defineProperty(exports, "CircuitOpenError", { enumerable: true, get: function () { return circuit_breaker_1.CircuitOpenError; } });
|
|
156
161
|
// Default export
|
|
157
162
|
exports.default = DotAI;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mermaid-tools.d.ts","sourceRoot":"","sources":["../../src/core/mermaid-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"mermaid-tools.d.ts","sourceRoot":"","sources":["../../src/core/mermaid-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AASjD;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,MA0BnC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,EAEjC,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,GAAG,GACT,OAAO,CAAC,GAAG,CAAC,CAoDd"}
|
|
@@ -17,9 +17,7 @@ const mermaid_1 = __importDefault(require("mermaid"));
|
|
|
17
17
|
// Initialize mermaid with strict validation
|
|
18
18
|
mermaid_1.default.initialize({
|
|
19
19
|
securityLevel: 'strict',
|
|
20
|
-
startOnLoad: false
|
|
21
|
-
// Disable rendering, we only want parsing/validation
|
|
22
|
-
suppressErrorRendering: true
|
|
20
|
+
startOnLoad: false
|
|
23
21
|
});
|
|
24
22
|
/**
|
|
25
23
|
* Tool: validate_mermaid
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unified-creation-session.d.ts","sourceRoot":"","sources":["../../src/core/unified-creation-session.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAKlD,OAAO,EACL,sBAAsB,EAEtB,2BAA2B,EAC3B,iCAAiC,EACjC,UAAU,EAIX,MAAM,0BAA0B,CAAC;AAKlC,qBAAa,6BAA6B;IACxC,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,cAAc,CAAoD;gBAE9D,UAAU,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,mBAAmB;IAMnE;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,GAAG,GAAG,sBAAsB;IAWhD;;OAEG;IACH,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,sBAAsB,GAAG,IAAI;IAIzE;;OAEG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,sBAAsB;
|
|
1
|
+
{"version":3,"file":"unified-creation-session.d.ts","sourceRoot":"","sources":["../../src/core/unified-creation-session.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAKlD,OAAO,EACL,sBAAsB,EAEtB,2BAA2B,EAC3B,iCAAiC,EACjC,UAAU,EAIX,MAAM,0BAA0B,CAAC;AAKlC,qBAAa,6BAA6B;IACxC,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,cAAc,CAAoD;gBAE9D,UAAU,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,mBAAmB;IAMnE;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,GAAG,GAAG,sBAAsB;IAWhD;;OAEG;IACH,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,sBAAsB,GAAG,IAAI;IAIzE;;OAEG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,sBAAsB;IAiHxF;;OAEG;IACG,mBAAmB,CAAC,OAAO,EAAE,sBAAsB,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,2BAA2B,GAAG,iCAAiC,CAAC;IAoGhJ;;OAEG;YACW,4BAA4B;IA4C1C;;OAEG;YACW,gCAAgC;IAyC9C;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA6E1B;;OAEG;YACW,gBAAgB;IAsF9B;;OAEG;YACW,4BAA4B;IAoH1C;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAY1B;;OAEG;YACW,qBAAqB;IAqCnC;;OAEG;YACW,mBAAmB;IAyLjC;;OAEG;YACW,uBAAuB;IAsGrC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAU5B;;OAEG;IACH,OAAO,CAAC,sBAAsB;CAY/B"}
|
|
@@ -141,6 +141,13 @@ class UnifiedCreationSessionManager {
|
|
|
141
141
|
const namespaces = scopeChoice.replace('exclude:', '').split(',').map(ns => ns.trim()).filter(ns => ns.length > 0);
|
|
142
142
|
session.data.namespaceScope = { type: 'exclude', namespaces };
|
|
143
143
|
}
|
|
144
|
+
else {
|
|
145
|
+
// Treat any other input as namespace names to include (e.g., "policy-test" or "ns1, ns2")
|
|
146
|
+
const namespaces = scopeChoice.split(',').map(ns => ns.trim()).filter(ns => ns.length > 0);
|
|
147
|
+
if (namespaces.length > 0) {
|
|
148
|
+
session.data.namespaceScope = { type: 'include', namespaces };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
144
151
|
session.data.currentStep = (0, unified_creation_types_1.getNextStep)('namespace-scope', this.config);
|
|
145
152
|
break;
|
|
146
153
|
}
|
|
@@ -43,6 +43,11 @@ export declare class VectorDBService {
|
|
|
43
43
|
* Handles conflict errors gracefully (collection already exists from race condition or restart)
|
|
44
44
|
*/
|
|
45
45
|
private createCollection;
|
|
46
|
+
/**
|
|
47
|
+
* Ensure text index exists on searchText field for efficient keyword search
|
|
48
|
+
* This is idempotent - safe to call multiple times
|
|
49
|
+
*/
|
|
50
|
+
private ensureTextIndex;
|
|
46
51
|
/**
|
|
47
52
|
* Store a document with optional vector
|
|
48
53
|
*/
|
|
@@ -53,6 +58,7 @@ export declare class VectorDBService {
|
|
|
53
58
|
searchSimilar(vector: number[], options?: SearchOptions): Promise<SearchResult[]>;
|
|
54
59
|
/**
|
|
55
60
|
* Search for documents using payload filtering (keyword search)
|
|
61
|
+
* Uses Qdrant's native text index for efficient server-side filtering
|
|
56
62
|
*/
|
|
57
63
|
searchByKeywords(keywords: string[], options?: SearchOptions): Promise<SearchResult[]>;
|
|
58
64
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vector-db-service.d.ts","sourceRoot":"","sources":["../../src/core/vector-db-service.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAgDH,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,cAAc,CAAS;gBAEnB,MAAM,GAAE,cAAmB;
|
|
1
|
+
{"version":3,"file":"vector-db-service.d.ts","sourceRoot":"","sources":["../../src/core/vector-db-service.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAgDH,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,cAAc,CAAS;gBAEnB,MAAM,GAAE,cAAmB;IAuBvC,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,sBAAsB;IAM9B;;OAEG;IACG,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC;IAa1C;;OAEG;IACG,oBAAoB,CAAC,UAAU,GAAE,MAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAoDnE;;;OAGG;YACW,gBAAgB;IAmC9B;;;OAGG;YACW,eAAe;IAwC7B;;OAEG;IACG,cAAc,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAqC7D;;OAEG;IACG,aAAa,CACjB,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,EAAE,CAAC;IAuC1B;;;OAGG;IACG,gBAAgB,CACpB,QAAQ,EAAE,MAAM,EAAE,EAClB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,EAAE,CAAC;IA8H1B;;OAEG;IACG,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAqC7D;;OAEG;IACG,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA8B/C;;;;OAIG;IACG,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAuCzC;;;;;OAKG;IACG,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,GAAE,MAAY,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAuCnF;;;;OAIG;IACG,eAAe,CAAC,KAAK,GAAE,MAAc,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAgDvE;;OAEG;IACG,iBAAiB,IAAI,OAAO,CAAC,GAAG,CAAC;IAqBvC;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAsBrC;;OAEG;IACH,aAAa,IAAI,OAAO;IAIxB;;OAEG;IACH,SAAS,IAAI,cAAc;CAG5B"}
|
|
@@ -44,8 +44,8 @@ class QdrantSemaphore {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
// Limit
|
|
48
|
-
const qdrantSemaphore = new QdrantSemaphore(
|
|
47
|
+
// Limit concurrent Qdrant bulk operations (scroll, getAllDocuments)
|
|
48
|
+
const qdrantSemaphore = new QdrantSemaphore(100);
|
|
49
49
|
class VectorDBService {
|
|
50
50
|
client = null;
|
|
51
51
|
config;
|
|
@@ -64,7 +64,8 @@ class VectorDBService {
|
|
|
64
64
|
if (this.shouldInitializeClient()) {
|
|
65
65
|
this.client = new js_client_rest_1.QdrantClient({
|
|
66
66
|
url: this.config.url,
|
|
67
|
-
apiKey: this.config.apiKey
|
|
67
|
+
apiKey: this.config.apiKey,
|
|
68
|
+
maxConnections: 100, // HTTP keep-alive pool for connection reuse
|
|
68
69
|
});
|
|
69
70
|
}
|
|
70
71
|
}
|
|
@@ -125,6 +126,10 @@ class VectorDBService {
|
|
|
125
126
|
await this.client.deleteCollection(this.collectionName);
|
|
126
127
|
await this.createCollection(vectorSize);
|
|
127
128
|
}
|
|
129
|
+
else {
|
|
130
|
+
// Ensure text index exists for existing collections (transparent upgrade)
|
|
131
|
+
await this.ensureTextIndex();
|
|
132
|
+
}
|
|
128
133
|
}
|
|
129
134
|
catch (error) {
|
|
130
135
|
// If we can't get collection info, assume it's corrupted and recreate
|
|
@@ -158,11 +163,12 @@ class VectorDBService {
|
|
|
158
163
|
distance: 'Cosine',
|
|
159
164
|
on_disk: true // Enable on-disk storage for better performance with large collections
|
|
160
165
|
},
|
|
161
|
-
// Enable payload indexing for better keyword search performance
|
|
162
166
|
optimizers_config: {
|
|
163
167
|
default_segment_number: 2
|
|
164
168
|
}
|
|
165
169
|
});
|
|
170
|
+
// Create text index on searchText field for efficient keyword search
|
|
171
|
+
await this.ensureTextIndex();
|
|
166
172
|
}
|
|
167
173
|
catch (error) {
|
|
168
174
|
// Handle race condition where collection was created between check and create
|
|
@@ -173,11 +179,53 @@ class VectorDBService {
|
|
|
173
179
|
if (process.env.DEBUG_DOT_AI) {
|
|
174
180
|
console.debug(`Collection ${this.collectionName} already exists, skipping creation`);
|
|
175
181
|
}
|
|
182
|
+
await this.ensureTextIndex();
|
|
176
183
|
return;
|
|
177
184
|
}
|
|
178
185
|
throw error;
|
|
179
186
|
}
|
|
180
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Ensure text index exists on searchText field for efficient keyword search
|
|
190
|
+
* This is idempotent - safe to call multiple times
|
|
191
|
+
*/
|
|
192
|
+
async ensureTextIndex() {
|
|
193
|
+
if (!this.client) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
// Check if index already exists
|
|
198
|
+
const collectionInfo = await this.client.getCollection(this.collectionName);
|
|
199
|
+
const payloadSchema = collectionInfo.payload_schema || {};
|
|
200
|
+
// Check if searchText already has a text index
|
|
201
|
+
const searchTextIndex = payloadSchema['searchText'];
|
|
202
|
+
if (searchTextIndex && searchTextIndex.data_type === 'text') {
|
|
203
|
+
return; // Index already exists
|
|
204
|
+
}
|
|
205
|
+
// Create text index on searchText field
|
|
206
|
+
await this.client.createPayloadIndex(this.collectionName, {
|
|
207
|
+
field_name: 'searchText',
|
|
208
|
+
field_schema: 'text',
|
|
209
|
+
});
|
|
210
|
+
if (process.env.DEBUG_DOT_AI) {
|
|
211
|
+
console.debug(`Created text index on searchText field for collection ${this.collectionName}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
216
|
+
const isAlreadyExists = errorMessage.toLowerCase().includes('already exists') ||
|
|
217
|
+
errorMessage.toLowerCase().includes('conflict');
|
|
218
|
+
if (isAlreadyExists) {
|
|
219
|
+
if (process.env.DEBUG_DOT_AI) {
|
|
220
|
+
console.debug(`Text index already exists for collection ${this.collectionName}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
// Unexpected error - log at warn level for visibility
|
|
225
|
+
console.warn(`Failed to create text index on ${this.collectionName}: ${errorMessage}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
181
229
|
/**
|
|
182
230
|
* Store a document with optional vector
|
|
183
231
|
*/
|
|
@@ -250,12 +298,16 @@ class VectorDBService {
|
|
|
250
298
|
}
|
|
251
299
|
/**
|
|
252
300
|
* Search for documents using payload filtering (keyword search)
|
|
301
|
+
* Uses Qdrant's native text index for efficient server-side filtering
|
|
253
302
|
*/
|
|
254
303
|
async searchByKeywords(keywords, options = {}) {
|
|
255
304
|
if (!this.client) {
|
|
256
305
|
throw new Error('Vector DB client not initialized');
|
|
257
306
|
}
|
|
258
307
|
const limit = options.limit || 10;
|
|
308
|
+
if (keywords.length === 0) {
|
|
309
|
+
return [];
|
|
310
|
+
}
|
|
259
311
|
return (0, qdrant_tracing_1.withQdrantTracing)({
|
|
260
312
|
operation: 'vector.search_keywords',
|
|
261
313
|
collectionName: this.collectionName,
|
|
@@ -264,15 +316,50 @@ class VectorDBService {
|
|
|
264
316
|
serverUrl: this.config.url
|
|
265
317
|
}, async () => {
|
|
266
318
|
try {
|
|
267
|
-
//
|
|
268
|
-
//
|
|
319
|
+
// Build Qdrant filter for text search
|
|
320
|
+
// Use "should" (OR) to match any keyword in searchText or triggers
|
|
321
|
+
const keywordConditions = [];
|
|
322
|
+
for (const keyword of keywords) {
|
|
323
|
+
// Text match on searchText field (uses text index)
|
|
324
|
+
keywordConditions.push({
|
|
325
|
+
key: 'searchText',
|
|
326
|
+
match: { text: keyword }
|
|
327
|
+
});
|
|
328
|
+
// Match on triggers array (for patterns/policies)
|
|
329
|
+
keywordConditions.push({
|
|
330
|
+
key: 'triggers',
|
|
331
|
+
match: { any: [keyword, keyword.toLowerCase()] }
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
// Combine keyword conditions with any user-provided filter
|
|
335
|
+
const filter = {
|
|
336
|
+
should: keywordConditions
|
|
337
|
+
};
|
|
338
|
+
// If user provided additional filters, merge them properly
|
|
339
|
+
if (options.filter) {
|
|
340
|
+
// Merge must conditions
|
|
341
|
+
if (options.filter.must) {
|
|
342
|
+
filter.must = Array.isArray(options.filter.must)
|
|
343
|
+
? options.filter.must
|
|
344
|
+
: [options.filter.must];
|
|
345
|
+
}
|
|
346
|
+
// Preserve must_not conditions
|
|
347
|
+
if (options.filter.must_not) {
|
|
348
|
+
filter.must_not = options.filter.must_not;
|
|
349
|
+
}
|
|
350
|
+
// If user filter has should conditions, wrap in must to AND with keyword should
|
|
351
|
+
if (options.filter.should) {
|
|
352
|
+
filter.must = [...(filter.must || []), { should: options.filter.should }];
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// Use scroll with native Qdrant filtering - much faster than client-side
|
|
269
356
|
const scrollResult = await this.client.scroll(this.collectionName, {
|
|
270
|
-
limit:
|
|
357
|
+
limit: limit * 10, // Get more candidates for scoring, but not 1000
|
|
271
358
|
with_payload: true,
|
|
272
359
|
with_vector: false,
|
|
273
|
-
|
|
360
|
+
filter
|
|
274
361
|
});
|
|
275
|
-
//
|
|
362
|
+
// Score the filtered results (small set now)
|
|
276
363
|
const scoredPoints = scrollResult.points
|
|
277
364
|
.map(point => {
|
|
278
365
|
if (!point.payload)
|
|
@@ -286,35 +373,27 @@ class VectorDBService {
|
|
|
286
373
|
let exactMatch = false;
|
|
287
374
|
for (const keyword of keywords) {
|
|
288
375
|
const kw = keyword.toLowerCase();
|
|
289
|
-
// Check searchText
|
|
376
|
+
// Check searchText
|
|
290
377
|
if (searchText.includes(kw)) {
|
|
291
378
|
matchCount++;
|
|
292
|
-
// Bonus for exact word match (surrounded by spaces/punctuation)
|
|
293
379
|
const wordPattern = new RegExp(`\\b${escapeRegExp(kw)}\\b`, 'i');
|
|
294
380
|
if (wordPattern.test(searchText)) {
|
|
295
381
|
exactMatch = true;
|
|
296
382
|
}
|
|
297
383
|
}
|
|
298
|
-
// Check triggers
|
|
384
|
+
// Check triggers
|
|
299
385
|
if (triggers.some(t => t.includes(kw) || kw.includes(t))) {
|
|
300
386
|
matchCount++;
|
|
301
387
|
}
|
|
302
388
|
}
|
|
303
389
|
if (matchCount === 0)
|
|
304
390
|
return null;
|
|
305
|
-
// Score based on match quality
|
|
306
|
-
// - Base score from match ratio
|
|
307
|
-
// - Bonus for exact word matches
|
|
308
391
|
const baseScore = matchCount / keywords.length;
|
|
309
392
|
const score = exactMatch ? Math.min(1.0, baseScore + 0.3) : baseScore;
|
|
310
|
-
return {
|
|
311
|
-
point,
|
|
312
|
-
score
|
|
313
|
-
};
|
|
393
|
+
return { point, score };
|
|
314
394
|
})
|
|
315
395
|
.filter((item) => item !== null)
|
|
316
396
|
.sort((a, b) => b.score - a.score);
|
|
317
|
-
// Apply limit after filtering
|
|
318
397
|
const limitedResults = scoredPoints.slice(0, limit);
|
|
319
398
|
return limitedResults.map(({ point, score }) => ({
|
|
320
399
|
id: point.id.toString(),
|
|
@@ -10,9 +10,9 @@ export declare const ANSWERQUESTION_TOOL_INPUT_SCHEMA: {
|
|
|
10
10
|
solutionId: z.ZodString;
|
|
11
11
|
stage: z.ZodEnum<{
|
|
12
12
|
required: "required";
|
|
13
|
+
open: "open";
|
|
13
14
|
basic: "basic";
|
|
14
15
|
advanced: "advanced";
|
|
15
|
-
open: "open";
|
|
16
16
|
}>;
|
|
17
17
|
answers: z.ZodRecord<z.ZodString, z.ZodAny>;
|
|
18
18
|
interaction_id: z.ZodOptional<z.ZodString>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vfarcic/dot-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.195.0",
|
|
4
4
|
"description": "AI-powered development productivity platform that enhances software development workflows through intelligent automation and AI-driven assistance",
|
|
5
5
|
"mcpName": "io.github.vfarcic/dot-ai",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"@qdrant/js-client-rest": "^1.15.0",
|
|
115
115
|
"ai": "^5.0.60",
|
|
116
116
|
"handlebars": "^4.7.8",
|
|
117
|
-
"mermaid": "^
|
|
117
|
+
"mermaid": "^10.9.5",
|
|
118
118
|
"posthog-node": "^5.23.0",
|
|
119
119
|
"yaml": "^2.8.0",
|
|
120
120
|
"zod-to-json-schema": "^3.24.6"
|