@juspay/yama 1.4.1 → 1.5.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/CHANGELOG.md +12 -0
- package/dist/core/providers/BitbucketProvider.js +36 -23
- package/dist/features/CodeReviewer.d.ts +12 -0
- package/dist/features/CodeReviewer.js +189 -6
- package/dist/features/MultiInstanceProcessor.js +3 -2
- package/dist/types/index.d.ts +38 -0
- package/dist/types/index.js +65 -0
- package/dist/utils/Cache.d.ts +8 -2
- package/dist/utils/Cache.js +190 -10
- package/dist/utils/ParallelProcessing.d.ts +28 -0
- package/dist/utils/ParallelProcessing.js +108 -3
- package/dist/utils/RetryManager.d.ts +78 -0
- package/dist/utils/RetryManager.js +205 -0
- package/package.json +1 -1
- package/yama.config.example.yaml +1 -0
package/dist/utils/Cache.js
CHANGED
|
@@ -3,12 +3,146 @@
|
|
|
3
3
|
* Provides intelligent caching for PR data, file contents, and AI responses
|
|
4
4
|
*/
|
|
5
5
|
import NodeCache from "node-cache";
|
|
6
|
+
import { CacheError, } from "../types/index.js";
|
|
6
7
|
import { logger } from "./Logger.js";
|
|
8
|
+
/**
|
|
9
|
+
* Enhanced cache error detection utility
|
|
10
|
+
* Provides multi-layer error classification to avoid false positives
|
|
11
|
+
*/
|
|
12
|
+
class CacheErrorDetector {
|
|
13
|
+
/**
|
|
14
|
+
* Detect if an error is cache-related using multiple strategies
|
|
15
|
+
*/
|
|
16
|
+
static isCacheError(error, operation, key) {
|
|
17
|
+
// Strategy 1: Check error type/class (most reliable)
|
|
18
|
+
if (error instanceof CacheError) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
// Strategy 2: Check for specific cache error patterns in NodeCache
|
|
22
|
+
if (error instanceof Error) {
|
|
23
|
+
const errorMessage = error.message.toLowerCase();
|
|
24
|
+
const stackTrace = error.stack?.toLowerCase() || "";
|
|
25
|
+
// Check for NodeCache-specific error patterns
|
|
26
|
+
const nodeCachePatterns = [
|
|
27
|
+
/node_modules\/node-cache/,
|
|
28
|
+
/cache\.js:\d+/,
|
|
29
|
+
/nodecache/,
|
|
30
|
+
];
|
|
31
|
+
const isNodeCacheError = nodeCachePatterns.some((pattern) => pattern.test(stackTrace));
|
|
32
|
+
if (isNodeCacheError) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
// Strategy 3: Check for specific cache-related error messages (more targeted)
|
|
36
|
+
const cacheSpecificPatterns = [
|
|
37
|
+
/cache.*(?:full|exhausted|limit)/,
|
|
38
|
+
/memory.*(?:cache|allocation).*(?:failed|error)/,
|
|
39
|
+
/storage.*(?:cache|quota).*(?:exceeded|full)/,
|
|
40
|
+
/cache.*(?:initialization|setup).*(?:failed|error)/,
|
|
41
|
+
/ttl.*(?:invalid|expired)/,
|
|
42
|
+
/cache.*(?:key|value).*(?:invalid|malformed)/,
|
|
43
|
+
];
|
|
44
|
+
const hasCacheSpecificError = cacheSpecificPatterns.some((pattern) => pattern.test(errorMessage));
|
|
45
|
+
if (hasCacheSpecificError) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
// Strategy 4: Context-aware detection
|
|
49
|
+
if (operation && key) {
|
|
50
|
+
// If we're in a cache operation and get memory/storage errors, likely cache-related
|
|
51
|
+
const cacheOperations = [
|
|
52
|
+
"get",
|
|
53
|
+
"set",
|
|
54
|
+
"del",
|
|
55
|
+
"clear",
|
|
56
|
+
"has",
|
|
57
|
+
"getorset",
|
|
58
|
+
"getorsetresilient",
|
|
59
|
+
];
|
|
60
|
+
const isCacheOperation = cacheOperations.includes(operation.toLowerCase());
|
|
61
|
+
const contextualPatterns = [
|
|
62
|
+
/^out of memory$/,
|
|
63
|
+
/storage quota exceeded/,
|
|
64
|
+
/disk full/,
|
|
65
|
+
];
|
|
66
|
+
if (isCacheOperation &&
|
|
67
|
+
contextualPatterns.some((pattern) => pattern.test(errorMessage))) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Classify cache error for better handling and logging
|
|
76
|
+
*/
|
|
77
|
+
static classifyError(error, operation, key) {
|
|
78
|
+
if (!this.isCacheError(error, operation, key)) {
|
|
79
|
+
return {
|
|
80
|
+
isCache: false,
|
|
81
|
+
category: "unknown",
|
|
82
|
+
confidence: "high",
|
|
83
|
+
reason: "Not identified as cache-related error",
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (error instanceof CacheError) {
|
|
87
|
+
const category = error.code.includes("STORAGE")
|
|
88
|
+
? "storage"
|
|
89
|
+
: error.code.includes("NETWORK")
|
|
90
|
+
? "network"
|
|
91
|
+
: error.code.includes("SYSTEM")
|
|
92
|
+
? "system"
|
|
93
|
+
: "operation";
|
|
94
|
+
return {
|
|
95
|
+
isCache: true,
|
|
96
|
+
category,
|
|
97
|
+
confidence: "high",
|
|
98
|
+
reason: `Explicit cache error: ${error.code}`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (error instanceof Error) {
|
|
102
|
+
const message = error.message.toLowerCase();
|
|
103
|
+
const stack = error.stack?.toLowerCase() || "";
|
|
104
|
+
// High confidence patterns
|
|
105
|
+
if (/node_modules\/node-cache/.test(stack)) {
|
|
106
|
+
return {
|
|
107
|
+
isCache: true,
|
|
108
|
+
category: "system",
|
|
109
|
+
confidence: "high",
|
|
110
|
+
reason: "NodeCache stack trace detected",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// Medium confidence patterns
|
|
114
|
+
if (/cache.*(?:full|exhausted)/.test(message)) {
|
|
115
|
+
return {
|
|
116
|
+
isCache: true,
|
|
117
|
+
category: "storage",
|
|
118
|
+
confidence: "medium",
|
|
119
|
+
reason: "Cache capacity error pattern",
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (/memory.*cache.*failed/.test(message)) {
|
|
123
|
+
return {
|
|
124
|
+
isCache: true,
|
|
125
|
+
category: "system",
|
|
126
|
+
confidence: "medium",
|
|
127
|
+
reason: "Memory allocation error in cache context",
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
isCache: true,
|
|
133
|
+
category: "unknown",
|
|
134
|
+
confidence: "low",
|
|
135
|
+
reason: "Fallback detection",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
7
139
|
export class Cache {
|
|
8
140
|
cache;
|
|
9
141
|
statsData = {
|
|
10
142
|
hits: 0,
|
|
11
143
|
misses: 0,
|
|
144
|
+
cacheErrors: 0,
|
|
145
|
+
nonCacheErrors: 0,
|
|
12
146
|
};
|
|
13
147
|
constructor(options = {}) {
|
|
14
148
|
const { ttl = 3600, // 1 hour default
|
|
@@ -33,18 +167,25 @@ export class Cache {
|
|
|
33
167
|
});
|
|
34
168
|
}
|
|
35
169
|
/**
|
|
36
|
-
* Get value from cache
|
|
170
|
+
* Get value from cache with resilient error handling
|
|
37
171
|
*/
|
|
38
172
|
get(key) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
173
|
+
try {
|
|
174
|
+
const value = this.cache.get(key);
|
|
175
|
+
if (value !== undefined) {
|
|
176
|
+
this.statsData.hits++;
|
|
177
|
+
logger.debug(`Cache HIT: ${key}`);
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
this.statsData.misses++;
|
|
182
|
+
logger.debug(`Cache MISS: ${key}`);
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
44
185
|
}
|
|
45
|
-
|
|
186
|
+
catch (error) {
|
|
46
187
|
this.statsData.misses++;
|
|
47
|
-
logger.
|
|
188
|
+
logger.warn(`Cache GET error for ${key}, treating as miss:`, error);
|
|
48
189
|
return undefined;
|
|
49
190
|
}
|
|
50
191
|
}
|
|
@@ -105,6 +246,8 @@ export class Cache {
|
|
|
105
246
|
misses: this.statsData.misses,
|
|
106
247
|
keys: this.cache.keys().length,
|
|
107
248
|
size: this.cache.getStats().keys,
|
|
249
|
+
cacheErrors: this.statsData.cacheErrors,
|
|
250
|
+
nonCacheErrors: this.statsData.nonCacheErrors,
|
|
108
251
|
};
|
|
109
252
|
}
|
|
110
253
|
/**
|
|
@@ -114,9 +257,10 @@ export class Cache {
|
|
|
114
257
|
return this.cache.getStats();
|
|
115
258
|
}
|
|
116
259
|
/**
|
|
117
|
-
* Get or set pattern
|
|
260
|
+
* Get or set pattern with automatic fallback on cache failures
|
|
118
261
|
*/
|
|
119
262
|
async getOrSet(key, fetchFn, ttl) {
|
|
263
|
+
// Try to get from cache with resilient error handling
|
|
120
264
|
const cached = this.get(key);
|
|
121
265
|
if (cached !== undefined) {
|
|
122
266
|
return cached;
|
|
@@ -124,7 +268,13 @@ export class Cache {
|
|
|
124
268
|
try {
|
|
125
269
|
logger.debug(`Cache FETCH: ${key}`);
|
|
126
270
|
const value = await fetchFn();
|
|
127
|
-
|
|
271
|
+
// Try to cache the result, but don't fail if caching fails
|
|
272
|
+
try {
|
|
273
|
+
this.set(key, value, ttl);
|
|
274
|
+
}
|
|
275
|
+
catch (cacheError) {
|
|
276
|
+
logger.warn(`Cache SET failed for ${key}, continuing without cache:`, cacheError);
|
|
277
|
+
}
|
|
128
278
|
return value;
|
|
129
279
|
}
|
|
130
280
|
catch (error) {
|
|
@@ -132,6 +282,36 @@ export class Cache {
|
|
|
132
282
|
throw error;
|
|
133
283
|
}
|
|
134
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* Resilient get or set pattern that bypasses cache entirely on cache system failures
|
|
287
|
+
*/
|
|
288
|
+
async getOrSetResilient(key, fetchFn, ttl) {
|
|
289
|
+
try {
|
|
290
|
+
// Try normal cache flow first
|
|
291
|
+
return await this.getOrSet(key, fetchFn, ttl);
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
// Use enhanced error detection to determine if this is a cache-related error
|
|
295
|
+
const errorClassification = CacheErrorDetector.classifyError(error, "getOrSet", key);
|
|
296
|
+
if (errorClassification.isCache) {
|
|
297
|
+
// Track cache error statistics
|
|
298
|
+
this.statsData.cacheErrors++;
|
|
299
|
+
logger.warn(`Cache system error detected for ${key} (${errorClassification.confidence} confidence: ${errorClassification.reason}), bypassing cache entirely`, {
|
|
300
|
+
error: error instanceof Error ? error.message : String(error),
|
|
301
|
+
category: errorClassification.category,
|
|
302
|
+
confidence: errorClassification.confidence,
|
|
303
|
+
key,
|
|
304
|
+
operation: "getOrSet",
|
|
305
|
+
});
|
|
306
|
+
// Bypass cache completely and just fetch the data
|
|
307
|
+
return await fetchFn();
|
|
308
|
+
}
|
|
309
|
+
// Track non-cache errors for debugging
|
|
310
|
+
this.statsData.nonCacheErrors++;
|
|
311
|
+
// Re-throw non-cache errors
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
135
315
|
/**
|
|
136
316
|
* Cache with tags for group invalidation
|
|
137
317
|
*/
|
|
@@ -46,6 +46,9 @@ export declare class TokenBudgetManager implements TokenBudgetManagerInterface {
|
|
|
46
46
|
private usedTokens;
|
|
47
47
|
private batchAllocations;
|
|
48
48
|
private reservedTokens;
|
|
49
|
+
private preAllocationMode;
|
|
50
|
+
private preAllocatedBatches;
|
|
51
|
+
private batchStates;
|
|
49
52
|
constructor(totalBudget: number);
|
|
50
53
|
/**
|
|
51
54
|
* Allocate tokens for a specific batch
|
|
@@ -92,6 +95,31 @@ export declare class TokenBudgetManager implements TokenBudgetManagerInterface {
|
|
|
92
95
|
* Reset the budget manager (for testing or reuse)
|
|
93
96
|
*/
|
|
94
97
|
reset(): void;
|
|
98
|
+
/**
|
|
99
|
+
* Pre-allocate tokens for all batches upfront
|
|
100
|
+
* This ensures all batches have guaranteed token allocation before processing starts
|
|
101
|
+
*/
|
|
102
|
+
preAllocateAllBatches(allocations: Map<number, number>): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Mark a batch as failed and handle cleanup
|
|
105
|
+
*/
|
|
106
|
+
markBatchFailed(batchIndex: number, error?: string): void;
|
|
107
|
+
/**
|
|
108
|
+
* Get the current state of a batch
|
|
109
|
+
*/
|
|
110
|
+
getBatchState(batchIndex: number): "pending" | "processing" | "completed" | "failed" | undefined;
|
|
111
|
+
/**
|
|
112
|
+
* Check if pre-allocation mode is active
|
|
113
|
+
*/
|
|
114
|
+
isPreAllocationMode(): boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Get all batch states for debugging
|
|
117
|
+
*/
|
|
118
|
+
getAllBatchStates(): Map<number, "pending" | "processing" | "completed" | "failed">;
|
|
119
|
+
/**
|
|
120
|
+
* Disable pre-allocation mode and clean up
|
|
121
|
+
*/
|
|
122
|
+
disablePreAllocationMode(): void;
|
|
95
123
|
/**
|
|
96
124
|
* Update the total budget (useful for dynamic adjustment)
|
|
97
125
|
*/
|
|
@@ -77,12 +77,18 @@ export class TokenBudgetManager {
|
|
|
77
77
|
usedTokens = 0;
|
|
78
78
|
batchAllocations = new Map();
|
|
79
79
|
reservedTokens = 0; // Tokens allocated but not yet used
|
|
80
|
+
// NEW: Pre-allocation mode tracking
|
|
81
|
+
preAllocationMode = false;
|
|
82
|
+
preAllocatedBatches = new Set();
|
|
83
|
+
batchStates = new Map();
|
|
80
84
|
constructor(totalBudget) {
|
|
81
85
|
if (totalBudget <= 0) {
|
|
82
86
|
throw new Error("Token budget must be greater than 0");
|
|
83
87
|
}
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
// Floor the budget to ensure integer arithmetic and avoid floating-point precision issues.
|
|
89
|
+
// The fractional part is discarded, so the budget is always rounded down.
|
|
90
|
+
this.totalBudget = Math.floor(totalBudget);
|
|
91
|
+
logger.debug(`TokenBudgetManager created with budget of ${this.totalBudget} tokens (original: ${totalBudget})`);
|
|
86
92
|
}
|
|
87
93
|
/**
|
|
88
94
|
* Allocate tokens for a specific batch
|
|
@@ -93,8 +99,31 @@ export class TokenBudgetManager {
|
|
|
93
99
|
logger.warn(`Invalid token estimate for batch ${batchIndex}: ${estimatedTokens}`);
|
|
94
100
|
return false;
|
|
95
101
|
}
|
|
96
|
-
//
|
|
102
|
+
// NEW: Handle pre-allocation mode
|
|
103
|
+
if (this.preAllocationMode && this.preAllocatedBatches.has(batchIndex)) {
|
|
104
|
+
// Check if batch is already being processed
|
|
105
|
+
const currentState = this.batchStates.get(batchIndex);
|
|
106
|
+
if (currentState === "processing") {
|
|
107
|
+
logger.warn(`Batch ${batchIndex} is already being processed`);
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
if (currentState !== "pending") {
|
|
111
|
+
logger.warn(`Batch ${batchIndex} is not in pending state (current: ${currentState})`);
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
// In pre-allocation mode, just mark batch as processing and return success
|
|
115
|
+
this.batchStates.set(batchIndex, "processing");
|
|
116
|
+
logger.debug(`Batch ${batchIndex} using pre-allocated tokens (${this.batchAllocations.get(batchIndex)} tokens)`);
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
// Check if we already have an allocation for this batch (non-pre-allocation mode)
|
|
97
120
|
if (this.batchAllocations.has(batchIndex)) {
|
|
121
|
+
// Check if batch is already being processed
|
|
122
|
+
const currentState = this.batchStates.get(batchIndex);
|
|
123
|
+
if (currentState === "processing") {
|
|
124
|
+
logger.warn(`Batch ${batchIndex} is already being processed`);
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
98
127
|
logger.warn(`Batch ${batchIndex} already has token allocation`);
|
|
99
128
|
return false;
|
|
100
129
|
}
|
|
@@ -108,6 +137,7 @@ export class TokenBudgetManager {
|
|
|
108
137
|
// Allocate the tokens
|
|
109
138
|
this.reservedTokens += estimatedTokens;
|
|
110
139
|
this.batchAllocations.set(batchIndex, estimatedTokens);
|
|
140
|
+
this.batchStates.set(batchIndex, "processing");
|
|
111
141
|
logger.debug(`Allocated ${estimatedTokens} tokens for batch ${batchIndex} ` +
|
|
112
142
|
`(${this.getAvailableBudget()} remaining)`);
|
|
113
143
|
return true;
|
|
@@ -122,10 +152,19 @@ export class TokenBudgetManager {
|
|
|
122
152
|
logger.warn(`No token allocation found for batch ${batchIndex}`);
|
|
123
153
|
return;
|
|
124
154
|
}
|
|
155
|
+
// Update batch state to completed only if not already failed
|
|
156
|
+
const currentState = this.batchStates.get(batchIndex);
|
|
157
|
+
if (currentState !== "failed") {
|
|
158
|
+
this.batchStates.set(batchIndex, "completed");
|
|
159
|
+
}
|
|
125
160
|
// Move from reserved to used (assuming the tokens were actually used)
|
|
126
161
|
this.reservedTokens -= allocated;
|
|
127
162
|
this.usedTokens += allocated;
|
|
128
163
|
this.batchAllocations.delete(batchIndex);
|
|
164
|
+
// Clean up pre-allocation tracking
|
|
165
|
+
if (this.preAllocationMode) {
|
|
166
|
+
this.preAllocatedBatches.delete(batchIndex);
|
|
167
|
+
}
|
|
129
168
|
logger.debug(`Released ${allocated} tokens from batch ${batchIndex} ` +
|
|
130
169
|
`(${this.getAvailableBudget()} now available)`);
|
|
131
170
|
}
|
|
@@ -182,6 +221,72 @@ export class TokenBudgetManager {
|
|
|
182
221
|
this.batchAllocations.clear();
|
|
183
222
|
logger.debug("TokenBudgetManager reset");
|
|
184
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Pre-allocate tokens for all batches upfront
|
|
226
|
+
* This ensures all batches have guaranteed token allocation before processing starts
|
|
227
|
+
*/
|
|
228
|
+
preAllocateAllBatches(allocations) {
|
|
229
|
+
const totalRequired = Array.from(allocations.values()).reduce((sum, tokens) => sum + tokens, 0);
|
|
230
|
+
if (totalRequired > this.totalBudget) {
|
|
231
|
+
logger.error(`Pre-allocation failed: total required (${totalRequired}) exceeds budget (${this.totalBudget})`);
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
// Clear any existing allocations and reset state
|
|
235
|
+
this.batchAllocations.clear();
|
|
236
|
+
this.reservedTokens = 0;
|
|
237
|
+
this.batchStates.clear();
|
|
238
|
+
this.preAllocatedBatches.clear();
|
|
239
|
+
// Enable pre-allocation mode
|
|
240
|
+
this.preAllocationMode = true;
|
|
241
|
+
// Reserve all tokens upfront
|
|
242
|
+
allocations.forEach((tokens, batchIndex) => {
|
|
243
|
+
this.batchAllocations.set(batchIndex, tokens);
|
|
244
|
+
this.reservedTokens += tokens;
|
|
245
|
+
this.preAllocatedBatches.add(batchIndex);
|
|
246
|
+
this.batchStates.set(batchIndex, "pending");
|
|
247
|
+
});
|
|
248
|
+
logger.info(`Pre-allocated ${totalRequired} tokens across ${allocations.size} batches ` +
|
|
249
|
+
`(${this.getAvailableBudget()} remaining)`);
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Mark a batch as failed and handle cleanup
|
|
254
|
+
*/
|
|
255
|
+
markBatchFailed(batchIndex, error) {
|
|
256
|
+
this.batchStates.set(batchIndex, "failed");
|
|
257
|
+
if (error) {
|
|
258
|
+
logger.debug(`Batch ${batchIndex} marked as failed: ${error}`);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
logger.debug(`Batch ${batchIndex} marked as failed`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Get the current state of a batch
|
|
266
|
+
*/
|
|
267
|
+
getBatchState(batchIndex) {
|
|
268
|
+
return this.batchStates.get(batchIndex);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Check if pre-allocation mode is active
|
|
272
|
+
*/
|
|
273
|
+
isPreAllocationMode() {
|
|
274
|
+
return this.preAllocationMode;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Get all batch states for debugging
|
|
278
|
+
*/
|
|
279
|
+
getAllBatchStates() {
|
|
280
|
+
return new Map(this.batchStates);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Disable pre-allocation mode and clean up
|
|
284
|
+
*/
|
|
285
|
+
disablePreAllocationMode() {
|
|
286
|
+
this.preAllocationMode = false;
|
|
287
|
+
this.preAllocatedBatches.clear();
|
|
288
|
+
logger.debug("Pre-allocation mode disabled");
|
|
289
|
+
}
|
|
185
290
|
/**
|
|
186
291
|
* Update the total budget (useful for dynamic adjustment)
|
|
187
292
|
*/
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry Manager for Yama
|
|
3
|
+
* Provides intelligent retry logic with exponential backoff for handling transient failures
|
|
4
|
+
*/
|
|
5
|
+
export interface RetryOptions {
|
|
6
|
+
maxAttempts?: number;
|
|
7
|
+
baseDelayMs?: number;
|
|
8
|
+
maxDelayMs?: number;
|
|
9
|
+
backoffMultiplier?: number;
|
|
10
|
+
jitterMs?: number;
|
|
11
|
+
retryableErrors?: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface RetryContext {
|
|
14
|
+
operation: string;
|
|
15
|
+
attempt: number;
|
|
16
|
+
maxAttempts: number;
|
|
17
|
+
lastError?: Error;
|
|
18
|
+
totalElapsed: number;
|
|
19
|
+
}
|
|
20
|
+
export declare class RetryManager {
|
|
21
|
+
private static readonly DEFAULT_OPTIONS;
|
|
22
|
+
/**
|
|
23
|
+
* Execute an operation with retry logic
|
|
24
|
+
*/
|
|
25
|
+
static withRetry<T>(operation: () => Promise<T>, context: string, options?: RetryOptions): Promise<T>;
|
|
26
|
+
/**
|
|
27
|
+
* Check if an error is retryable based on error patterns
|
|
28
|
+
*/
|
|
29
|
+
private static isRetryableError;
|
|
30
|
+
/**
|
|
31
|
+
* Calculate delay with exponential backoff and jitter
|
|
32
|
+
*/
|
|
33
|
+
private static calculateDelay;
|
|
34
|
+
/**
|
|
35
|
+
* Sleep for specified milliseconds
|
|
36
|
+
*/
|
|
37
|
+
private static sleep;
|
|
38
|
+
/**
|
|
39
|
+
* Create a retry wrapper function for a specific operation
|
|
40
|
+
*/
|
|
41
|
+
static createRetryWrapper<T extends any[], R>(fn: (...args: T) => Promise<R>, context: string, options?: RetryOptions): (...args: T) => Promise<R>;
|
|
42
|
+
/**
|
|
43
|
+
* Batch retry operations with individual retry logic
|
|
44
|
+
*/
|
|
45
|
+
static batchWithRetry<T>(operations: Array<{
|
|
46
|
+
fn: () => Promise<T>;
|
|
47
|
+
context: string;
|
|
48
|
+
}>, options?: RetryOptions & {
|
|
49
|
+
continueOnError?: boolean;
|
|
50
|
+
}): Promise<Array<{
|
|
51
|
+
success: boolean;
|
|
52
|
+
data?: T;
|
|
53
|
+
error?: Error;
|
|
54
|
+
context: string;
|
|
55
|
+
}>>;
|
|
56
|
+
/**
|
|
57
|
+
* Get retry statistics for monitoring
|
|
58
|
+
*/
|
|
59
|
+
static getRetryStats(results: Array<{
|
|
60
|
+
success: boolean;
|
|
61
|
+
context: string;
|
|
62
|
+
}>): {
|
|
63
|
+
total: number;
|
|
64
|
+
successful: number;
|
|
65
|
+
failed: number;
|
|
66
|
+
successRate: number;
|
|
67
|
+
failuresByContext: Record<string, number>;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Create a circuit breaker pattern (simple implementation)
|
|
71
|
+
*/
|
|
72
|
+
static createCircuitBreaker<T extends any[], R>(fn: (...args: T) => Promise<R>, context: string, options?: {
|
|
73
|
+
failureThreshold?: number;
|
|
74
|
+
recoveryTimeoutMs?: number;
|
|
75
|
+
retryOptions?: RetryOptions;
|
|
76
|
+
}): (...args: T) => Promise<R>;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=RetryManager.d.ts.map
|