@azumag/opencode-rate-limit-fallback 1.49.0 → 1.57.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.
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Pattern Learning from Rate Limit Errors
3
+ */
4
+ /**
5
+ * PatternLearner - Orchestrates the learning process from errors
6
+ */
7
+ export class PatternLearner {
8
+ extractor;
9
+ scorer;
10
+ storage;
11
+ config;
12
+ logger;
13
+ patternTracker = new Map();
14
+ learnedPatterns = new Map();
15
+ constructor(extractor, scorer, storage, config, logger) {
16
+ this.extractor = extractor;
17
+ this.scorer = scorer;
18
+ this.storage = storage;
19
+ this.config = config;
20
+ this.logger = logger;
21
+ // Note: Patterns will be loaded asynchronously via loadLearnedPatterns()
22
+ }
23
+ /**
24
+ * Learn from a rate limit error
25
+ */
26
+ learnFromError(error) {
27
+ if (!this.config.enabled) {
28
+ return;
29
+ }
30
+ try {
31
+ // Extract pattern candidates
32
+ const candidates = this.extractor.extractPatterns(error);
33
+ for (const candidate of candidates) {
34
+ this.trackPattern(candidate);
35
+ }
36
+ // Process and potentially save patterns
37
+ this.processPatterns();
38
+ }
39
+ catch (error) {
40
+ this.logger.error('[PatternLearner] Failed to learn from error', {
41
+ error: error instanceof Error ? error.message : String(error),
42
+ });
43
+ }
44
+ }
45
+ /**
46
+ * Track a pattern candidate for learning
47
+ */
48
+ trackPattern(candidate) {
49
+ // Generate a key for this pattern
50
+ const key = this.generatePatternKey(candidate);
51
+ const existing = this.patternTracker.get(key);
52
+ if (existing) {
53
+ // Update existing tracker
54
+ existing.lastSeen = Date.now();
55
+ existing.count++;
56
+ existing.samples.push(candidate.sourceError);
57
+ // Keep only last 10 samples
58
+ if (existing.samples.length > 10) {
59
+ existing.samples.shift();
60
+ }
61
+ }
62
+ else {
63
+ // Create new tracker
64
+ this.patternTracker.set(key, {
65
+ pattern: candidate,
66
+ firstSeen: Date.now(),
67
+ lastSeen: Date.now(),
68
+ count: 1,
69
+ samples: [candidate.sourceError],
70
+ });
71
+ }
72
+ }
73
+ /**
74
+ * Process tracked patterns and save those meeting criteria
75
+ */
76
+ async processPatterns() {
77
+ for (const [key, tracker] of this.patternTracker.entries()) {
78
+ // Check if pattern meets frequency threshold
79
+ if (tracker.count < this.config.minErrorFrequency) {
80
+ continue;
81
+ }
82
+ // Check if pattern already exists in learned patterns
83
+ if (this.learnedPatterns.has(key)) {
84
+ continue;
85
+ }
86
+ // Calculate confidence score
87
+ const confidence = this.scorer.calculateScore(tracker.pattern, tracker.count, tracker.firstSeen);
88
+ // Check if pattern meets auto-approve threshold
89
+ if (!this.scorer.shouldAutoApprove(confidence)) {
90
+ this.logger.debug(`[PatternLearner] Pattern confidence ${confidence.toFixed(2)} below threshold ${this.config.autoApproveThreshold}, not auto-approving`);
91
+ continue;
92
+ }
93
+ // Create learned pattern
94
+ const learnedPattern = {
95
+ name: this.generatePatternName(tracker.pattern),
96
+ provider: tracker.pattern.provider,
97
+ patterns: tracker.pattern.patterns,
98
+ priority: this.calculatePriority(tracker.pattern, confidence),
99
+ confidence,
100
+ learnedAt: new Date(tracker.firstSeen).toISOString(),
101
+ sampleCount: tracker.count,
102
+ lastUsed: undefined,
103
+ };
104
+ // Save to storage
105
+ await this.storage.savePattern(learnedPattern);
106
+ // Add to learned patterns
107
+ this.learnedPatterns.set(key, learnedPattern);
108
+ // Remove from tracker
109
+ this.patternTracker.delete(key);
110
+ this.logger.info(`[PatternLearner] Learned new pattern: ${learnedPattern.name} (confidence: ${confidence.toFixed(2)})`);
111
+ }
112
+ }
113
+ /**
114
+ * Merge similar patterns
115
+ */
116
+ mergePatterns(patterns) {
117
+ if (patterns.length === 0) {
118
+ return null;
119
+ }
120
+ if (patterns.length === 1) {
121
+ return patterns[0];
122
+ }
123
+ // Merge all patterns into one
124
+ const mergedPattern = {
125
+ provider: patterns[0].provider,
126
+ patterns: patterns.flatMap(p => p.patterns),
127
+ sourceError: patterns.map(p => p.sourceError).join('; '),
128
+ extractedAt: Math.max(...patterns.map(p => p.extractedAt)),
129
+ };
130
+ // Deduplicate patterns
131
+ mergedPattern.patterns = [...new Set(mergedPattern.patterns)];
132
+ return mergedPattern;
133
+ }
134
+ /**
135
+ * Load learned patterns from storage
136
+ */
137
+ async loadLearnedPatterns() {
138
+ try {
139
+ const patterns = await this.storage.loadPatterns();
140
+ for (const pattern of patterns) {
141
+ const key = this.generatePatternKeyFromLearned(pattern);
142
+ this.learnedPatterns.set(key, pattern);
143
+ }
144
+ this.logger.info(`[PatternLearner] Loaded ${patterns.length} learned patterns`);
145
+ }
146
+ catch (error) {
147
+ this.logger.error('[PatternLearner] Failed to load learned patterns', {
148
+ error: error instanceof Error ? error.message : String(error),
149
+ });
150
+ }
151
+ }
152
+ /**
153
+ * Get all learned patterns
154
+ */
155
+ getLearnedPatterns() {
156
+ return Array.from(this.learnedPatterns.values());
157
+ }
158
+ /**
159
+ * Get learned patterns for a specific provider
160
+ */
161
+ getLearnedPatternsForProvider(provider) {
162
+ return this.getLearnedPatterns().filter(p => !p.provider || p.provider === provider);
163
+ }
164
+ /**
165
+ * Add a learned pattern manually
166
+ */
167
+ async addLearnedPattern(pattern) {
168
+ const key = this.generatePatternKeyFromLearned(pattern);
169
+ this.learnedPatterns.set(key, pattern);
170
+ await this.storage.savePattern(pattern);
171
+ }
172
+ /**
173
+ * Remove a learned pattern
174
+ */
175
+ async removeLearnedPattern(name) {
176
+ const pattern = this.getLearnedPatternByName(name);
177
+ if (pattern) {
178
+ const key = this.generatePatternKeyFromLearned(pattern);
179
+ this.learnedPatterns.delete(key);
180
+ await this.storage.deletePattern(name);
181
+ return true;
182
+ }
183
+ return false;
184
+ }
185
+ /**
186
+ * Get a learned pattern by name
187
+ */
188
+ getLearnedPatternByName(name) {
189
+ return this.getLearnedPatterns().find(p => p.name === name);
190
+ }
191
+ /**
192
+ * Generate a unique key for a pattern candidate
193
+ */
194
+ generatePatternKey(pattern) {
195
+ const provider = pattern.provider || 'generic';
196
+ const patternStr = pattern.patterns.map(p => typeof p === 'string' ? p : p.source).join('|');
197
+ return `${provider}:${patternStr}`;
198
+ }
199
+ /**
200
+ * Generate a unique key for a learned pattern
201
+ */
202
+ generatePatternKeyFromLearned(pattern) {
203
+ const provider = pattern.provider || 'generic';
204
+ const patternStr = pattern.patterns.map(p => typeof p === 'string' ? p : p.source).join('|');
205
+ return `${provider}:${patternStr}`;
206
+ }
207
+ /**
208
+ * Generate a name for a pattern
209
+ */
210
+ generatePatternName(pattern) {
211
+ const provider = pattern.provider || 'generic';
212
+ const timestamp = Date.now();
213
+ return `learned-${provider}-${timestamp}`;
214
+ }
215
+ /**
216
+ * Calculate priority for a learned pattern
217
+ */
218
+ calculatePriority(pattern, confidence) {
219
+ // Base priority on confidence
220
+ const basePriority = Math.floor(confidence * 50);
221
+ // Add bonus for provider-specific patterns
222
+ const providerBonus = pattern.provider ? 10 : 0;
223
+ // Add bonus for number of patterns
224
+ const patternCountBonus = Math.min(pattern.patterns.length * 2, 10);
225
+ return Math.max(1, Math.min(100, basePriority + providerBonus + patternCountBonus));
226
+ }
227
+ /**
228
+ * Merge duplicate patterns in storage
229
+ */
230
+ async mergeDuplicatePatterns() {
231
+ return this.storage.mergeDuplicatePatterns();
232
+ }
233
+ /**
234
+ * Cleanup old patterns
235
+ */
236
+ async cleanupOldPatterns() {
237
+ const maxPatterns = this.config.maxLearnedPatterns;
238
+ return this.storage.cleanupOldPatterns(maxPatterns);
239
+ }
240
+ /**
241
+ * Clear all tracked patterns
242
+ */
243
+ clearTrackedPatterns() {
244
+ this.patternTracker.clear();
245
+ }
246
+ /**
247
+ * Get statistics about learning
248
+ */
249
+ getStats() {
250
+ return {
251
+ trackedPatterns: this.patternTracker.size,
252
+ learnedPatterns: this.learnedPatterns.size,
253
+ pendingPatterns: Array.from(this.patternTracker.values())
254
+ .filter(t => t.count >= this.config.minErrorFrequency).length,
255
+ };
256
+ }
257
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Error Pattern Registry for rate limit error detection
3
3
  */
4
- import type { ErrorPattern } from '../types/index.js';
4
+ import type { ErrorPattern, LearningConfig, LearnedPattern } from '../types/index.js';
5
5
  import { Logger } from '../../logger.js';
6
6
  /**
7
7
  * Error Pattern Registry class
@@ -9,8 +9,30 @@ import { Logger } from '../../logger.js';
9
9
  */
10
10
  export declare class ErrorPatternRegistry {
11
11
  private patterns;
12
+ private learnedPatterns;
13
+ private patternLearner?;
12
14
  private logger;
13
- constructor(logger?: Logger);
15
+ private configPath?;
16
+ constructor(logger?: Logger, config?: {
17
+ learningConfig?: LearningConfig;
18
+ configPath?: string;
19
+ });
20
+ /**
21
+ * Initialize pattern learning
22
+ */
23
+ private initializeLearning;
24
+ /**
25
+ * Load learned patterns from storage (async)
26
+ */
27
+ loadLearnedPatternsAsync(): Promise<void>;
28
+ /**
29
+ * Load learned patterns from storage (synchronous - kept for backward compatibility)
30
+ */
31
+ loadLearnedPatterns(): void;
32
+ /**
33
+ * Reload learned patterns (for config hot reload)
34
+ */
35
+ reloadLearnedPatterns(): Promise<void>;
14
36
  /**
15
37
  * Register default rate limit error patterns
16
38
  */
@@ -56,10 +78,37 @@ export declare class ErrorPatternRegistry {
56
78
  */
57
79
  resetToDefaults(): void;
58
80
  /**
59
- * Learn a new pattern from an error (for future ML-based learning)
60
- * Currently disabled - patterns must be manually registered
81
+ * Learn a new pattern from an error
82
+ */
83
+ addLearnedPattern(error: unknown): void;
84
+ /**
85
+ * Get all learned patterns
86
+ */
87
+ getLearnedPatterns(): LearnedPattern[];
88
+ /**
89
+ * Get a learned pattern by name
90
+ */
91
+ getLearnedPatternByName(name: string): LearnedPattern | undefined;
92
+ /**
93
+ * Remove a learned pattern by name
94
+ */
95
+ removeLearnedPattern(name: string): Promise<boolean>;
96
+ /**
97
+ * Merge duplicate learned patterns
98
+ */
99
+ mergeDuplicatePatterns(): Promise<number>;
100
+ /**
101
+ * Cleanup old learned patterns
102
+ */
103
+ cleanupOldPatterns(): Promise<number>;
104
+ /**
105
+ * Get learning statistics
61
106
  */
62
- addLearnedPattern(_error: unknown): void;
107
+ getLearningStats(): {
108
+ trackedPatterns: number;
109
+ learnedPatterns: number;
110
+ pendingPatterns: number;
111
+ } | null;
63
112
  /**
64
113
  * Get statistics about registered patterns
65
114
  */
@@ -1,14 +1,21 @@
1
1
  /**
2
2
  * Error Pattern Registry for rate limit error detection
3
3
  */
4
+ import { PatternExtractor } from './PatternExtractor.js';
5
+ import { ConfidenceScorer } from './ConfidenceScorer.js';
6
+ import { PatternStorage } from './PatternStorage.js';
7
+ import { PatternLearner } from './PatternLearner.js';
4
8
  /**
5
9
  * Error Pattern Registry class
6
10
  * Manages and matches error patterns for rate limit detection
7
11
  */
8
12
  export class ErrorPatternRegistry {
9
13
  patterns = [];
14
+ learnedPatterns = new Map();
15
+ patternLearner;
10
16
  logger;
11
- constructor(logger) {
17
+ configPath;
18
+ constructor(logger, config) {
12
19
  // Initialize logger
13
20
  this.logger = logger || {
14
21
  debug: () => { },
@@ -16,7 +23,89 @@ export class ErrorPatternRegistry {
16
23
  warn: () => { },
17
24
  error: () => { },
18
25
  };
26
+ this.configPath = config?.configPath;
19
27
  this.registerDefaultPatterns();
28
+ // Initialize pattern learning if enabled
29
+ if (config?.learningConfig && config.learningConfig.enabled) {
30
+ this.initializeLearning(config.learningConfig);
31
+ }
32
+ }
33
+ /**
34
+ * Initialize pattern learning
35
+ */
36
+ initializeLearning(learningConfig) {
37
+ if (!this.configPath) {
38
+ this.logger.warn('[ErrorPatternRegistry] Config path not provided, pattern learning disabled');
39
+ return;
40
+ }
41
+ try {
42
+ const extractor = new PatternExtractor();
43
+ const scorer = new ConfidenceScorer(learningConfig, this.patterns);
44
+ const storage = new PatternStorage(this.configPath, this.logger);
45
+ this.patternLearner = new PatternLearner(extractor, scorer, storage, learningConfig, this.logger);
46
+ // Note: Patterns will be loaded asynchronously via loadLearnedPatternsAsync()
47
+ this.logger.info('[ErrorPatternRegistry] Pattern learning enabled');
48
+ }
49
+ catch (error) {
50
+ this.logger.error('[ErrorPatternRegistry] Failed to initialize pattern learning', {
51
+ error: error instanceof Error ? error.message : String(error),
52
+ });
53
+ }
54
+ }
55
+ /**
56
+ * Load learned patterns from storage (async)
57
+ */
58
+ async loadLearnedPatternsAsync() {
59
+ if (!this.patternLearner) {
60
+ return;
61
+ }
62
+ try {
63
+ await this.patternLearner.loadLearnedPatterns();
64
+ const learnedPatterns = this.patternLearner.getLearnedPatterns();
65
+ for (const pattern of learnedPatterns) {
66
+ this.learnedPatterns.set(pattern.name, pattern);
67
+ this.register(pattern);
68
+ }
69
+ this.logger.info(`[ErrorPatternRegistry] Loaded ${learnedPatterns.length} learned patterns`);
70
+ }
71
+ catch (error) {
72
+ this.logger.error('[ErrorPatternRegistry] Failed to load learned patterns', {
73
+ error: error instanceof Error ? error.message : String(error),
74
+ });
75
+ }
76
+ }
77
+ /**
78
+ * Load learned patterns from storage (synchronous - kept for backward compatibility)
79
+ */
80
+ loadLearnedPatterns() {
81
+ if (!this.patternLearner) {
82
+ return;
83
+ }
84
+ try {
85
+ // Load patterns without awaiting (sync fallback)
86
+ this.patternLearner.loadLearnedPatterns().catch((error) => {
87
+ this.logger.error('[ErrorPatternRegistry] Failed to load learned patterns asynchronously', {
88
+ error: error instanceof Error ? error.message : String(error),
89
+ });
90
+ });
91
+ const learnedPatterns = this.patternLearner.getLearnedPatterns();
92
+ for (const pattern of learnedPatterns) {
93
+ this.learnedPatterns.set(pattern.name, pattern);
94
+ this.register(pattern);
95
+ }
96
+ }
97
+ catch (error) {
98
+ this.logger.error('[ErrorPatternRegistry] Failed to load learned patterns', {
99
+ error: error instanceof Error ? error.message : String(error),
100
+ });
101
+ }
102
+ }
103
+ /**
104
+ * Reload learned patterns (for config hot reload)
105
+ */
106
+ async reloadLearnedPatterns() {
107
+ this.learnedPatterns.clear();
108
+ await this.loadLearnedPatternsAsync();
20
109
  }
21
110
  /**
22
111
  * Register default rate limit error patterns
@@ -108,7 +197,13 @@ export class ErrorPatternRegistry {
108
197
  * Check if an error matches any registered rate limit pattern
109
198
  */
110
199
  isRateLimitError(error) {
111
- return this.getMatchedPattern(error) !== null;
200
+ // Check if this is a rate limit error
201
+ const isRateLimit = this.getMatchedPattern(error) !== null;
202
+ // If enabled, learn from this error
203
+ if (isRateLimit && this.patternLearner) {
204
+ this.patternLearner.learnFromError(error);
205
+ }
206
+ return isRateLimit;
112
207
  }
113
208
  /**
114
209
  * Get the matched pattern for an error, or null if no match
@@ -191,13 +286,81 @@ export class ErrorPatternRegistry {
191
286
  this.registerDefaultPatterns();
192
287
  }
193
288
  /**
194
- * Learn a new pattern from an error (for future ML-based learning)
195
- * Currently disabled - patterns must be manually registered
289
+ * Learn a new pattern from an error
290
+ */
291
+ addLearnedPattern(error) {
292
+ if (this.patternLearner) {
293
+ this.patternLearner.learnFromError(error);
294
+ }
295
+ else {
296
+ this.logger.warn('[ErrorPatternRegistry] Pattern learning is not enabled. Patterns must be manually registered via configuration.');
297
+ }
298
+ }
299
+ /**
300
+ * Get all learned patterns
301
+ */
302
+ getLearnedPatterns() {
303
+ if (!this.patternLearner) {
304
+ return [];
305
+ }
306
+ return this.patternLearner.getLearnedPatterns();
307
+ }
308
+ /**
309
+ * Get a learned pattern by name
310
+ */
311
+ getLearnedPatternByName(name) {
312
+ if (!this.patternLearner) {
313
+ return undefined;
314
+ }
315
+ return this.patternLearner.getLearnedPatternByName(name);
316
+ }
317
+ /**
318
+ * Remove a learned pattern by name
319
+ */
320
+ async removeLearnedPattern(name) {
321
+ if (!this.patternLearner) {
322
+ return false;
323
+ }
324
+ const removed = await this.patternLearner.removeLearnedPattern(name);
325
+ if (removed) {
326
+ this.removePattern(name);
327
+ }
328
+ return removed;
329
+ }
330
+ /**
331
+ * Merge duplicate learned patterns
332
+ */
333
+ async mergeDuplicatePatterns() {
334
+ if (!this.patternLearner) {
335
+ return 0;
336
+ }
337
+ const mergedCount = await this.patternLearner.mergeDuplicatePatterns();
338
+ if (mergedCount > 0) {
339
+ this.reloadLearnedPatterns();
340
+ }
341
+ return mergedCount;
342
+ }
343
+ /**
344
+ * Cleanup old learned patterns
196
345
  */
197
- addLearnedPattern(_error) {
198
- // Placeholder for future ML-based pattern learning
199
- // For now, patterns must be manually registered via config
200
- this.logger.warn('[ErrorPatternRegistry] Automatic pattern learning is not enabled. Patterns must be manually registered via configuration.');
346
+ async cleanupOldPatterns() {
347
+ if (!this.patternLearner) {
348
+ return 0;
349
+ }
350
+ const removedCount = await this.patternLearner.cleanupOldPatterns();
351
+ if (removedCount > 0) {
352
+ this.reloadLearnedPatterns();
353
+ }
354
+ return removedCount;
355
+ }
356
+ /**
357
+ * Get learning statistics
358
+ */
359
+ getLearningStats() {
360
+ if (!this.patternLearner) {
361
+ return null;
362
+ }
363
+ return this.patternLearner.getStats();
201
364
  }
202
365
  /**
203
366
  * Get statistics about registered patterns
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Pattern Storage for Learned Error Patterns
3
+ */
4
+ import type { LearnedPattern } from '../types/index.js';
5
+ import type { Logger } from '../../logger.js';
6
+ /**
7
+ * PatternStorage - Manages persistence of learned patterns to config file
8
+ */
9
+ export declare class PatternStorage {
10
+ private configPath;
11
+ private logger;
12
+ constructor(configPath: string, logger: Logger);
13
+ /**
14
+ * Save a learned pattern to config file
15
+ */
16
+ savePattern(pattern: LearnedPattern): Promise<void>;
17
+ /**
18
+ * Load all learned patterns from config file
19
+ */
20
+ loadPatterns(): Promise<LearnedPattern[]>;
21
+ /**
22
+ * Delete a pattern by name from config file
23
+ */
24
+ deletePattern(name: string): Promise<boolean>;
25
+ /**
26
+ * Merge duplicate patterns with high similarity
27
+ */
28
+ mergeDuplicatePatterns(): Promise<number>;
29
+ /**
30
+ * Cleanup old patterns, keeping only the most confident ones
31
+ */
32
+ cleanupOldPatterns(maxCount: number, patterns?: LearnedPattern[]): Promise<number>;
33
+ /**
34
+ * Load config file
35
+ */
36
+ private loadConfig;
37
+ /**
38
+ * Save config file
39
+ */
40
+ private saveConfig;
41
+ /**
42
+ * Validate pattern structure
43
+ */
44
+ private isValidPattern;
45
+ /**
46
+ * Calculate similarity between two patterns
47
+ */
48
+ private calculatePatternSimilarity;
49
+ }