@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,225 @@
1
+ /**
2
+ * Dynamic Prioritizer
3
+ * Dynamically prioritizes fallback models based on performance metrics
4
+ */
5
+ import { getModelKey } from '../utils/helpers.js';
6
+ /**
7
+ * Dynamic Prioritizer class for calculating dynamic model scores
8
+ */
9
+ export class DynamicPrioritizer {
10
+ config;
11
+ healthTracker;
12
+ logger;
13
+ metricsManager;
14
+ modelScores;
15
+ modelUsageHistory;
16
+ requestCount;
17
+ constructor(config, healthTracker, logger, metricsManager) {
18
+ this.config = config;
19
+ this.healthTracker = healthTracker;
20
+ this.logger = logger;
21
+ this.metricsManager = metricsManager;
22
+ this.modelScores = new Map();
23
+ this.modelUsageHistory = new Map();
24
+ this.requestCount = 0;
25
+ }
26
+ /**
27
+ * Record usage of a model for tracking recent activity
28
+ */
29
+ recordUsage(providerID, modelID) {
30
+ if (!this.config.enabled) {
31
+ return;
32
+ }
33
+ const key = getModelKey(providerID, modelID);
34
+ const now = Date.now();
35
+ let history = this.modelUsageHistory.get(key);
36
+ if (!history) {
37
+ history = [];
38
+ this.modelUsageHistory.set(key, history);
39
+ }
40
+ history.push(now);
41
+ // Trim history to max size
42
+ if (history.length > this.config.maxHistorySize) {
43
+ history.shift();
44
+ }
45
+ this.requestCount++;
46
+ }
47
+ /**
48
+ * Calculate dynamic score for a model
49
+ * Score is 0-1, higher is better
50
+ */
51
+ calculateScore(providerID, modelID) {
52
+ if (!this.config.enabled) {
53
+ return 0.5; // Neutral score when disabled
54
+ }
55
+ const key = getModelKey(providerID, modelID);
56
+ const health = this.healthTracker.getModelHealth(providerID, modelID);
57
+ // Default values if no health data
58
+ let healthScore = 100;
59
+ let avgResponseTime = 0;
60
+ let recentUsageScore = 0;
61
+ if (health) {
62
+ healthScore = health.healthScore;
63
+ avgResponseTime = health.avgResponseTime;
64
+ }
65
+ // Normalize health score (0-100 -> 0-1)
66
+ const normalizedHealthScore = healthScore / 100;
67
+ // Normalize response time (inverse - faster is better)
68
+ const normalizedResponseTime = this.normalizeResponseTime(avgResponseTime);
69
+ // Calculate recent usage score
70
+ recentUsageScore = this.calculateRecentUsageScore(key);
71
+ // Calculate weighted score
72
+ const score = normalizedHealthScore * this.config.successRateWeight +
73
+ normalizedResponseTime * this.config.responseTimeWeight +
74
+ recentUsageScore * this.config.recentUsageWeight;
75
+ this.modelScores.set(key, score);
76
+ return score;
77
+ }
78
+ /**
79
+ * Get prioritized models based on dynamic scores
80
+ * Returns models sorted by score (highest first)
81
+ */
82
+ getPrioritizedModels(candidates) {
83
+ if (!this.config.enabled) {
84
+ return candidates; // Return original order when disabled
85
+ }
86
+ // Check if we have enough samples for reliable ordering
87
+ if (!this.shouldUseDynamicOrdering()) {
88
+ return candidates;
89
+ }
90
+ // Map candidates with their scores
91
+ const scored = candidates.map(model => ({
92
+ model,
93
+ score: this.calculateScore(model.providerID, model.modelID),
94
+ }));
95
+ // Sort by score (descending)
96
+ scored.sort((a, b) => b.score - a.score);
97
+ // Check if the order actually changed (reorder occurred)
98
+ const reordered = candidates.map(m => getModelKey(m.providerID, m.modelID));
99
+ const sorted = scored.map(s => getModelKey(s.model.providerID, s.model.modelID));
100
+ const isReordered = JSON.stringify(reordered) !== JSON.stringify(sorted);
101
+ if (isReordered) {
102
+ // Record reorder event
103
+ if (this.metricsManager) {
104
+ this.metricsManager.recordDynamicPrioritizationReorder();
105
+ }
106
+ }
107
+ // Return sorted models
108
+ return scored.map(item => item.model);
109
+ }
110
+ /**
111
+ * Check if dynamic ordering should be used
112
+ * Returns true if dynamic prioritization is enabled and we have enough data for reliable ordering
113
+ */
114
+ shouldUseDynamicOrdering() {
115
+ if (!this.config.enabled) {
116
+ return false;
117
+ }
118
+ // Get health data for all tracked models
119
+ const healthData = this.healthTracker.getAllHealthData();
120
+ // Check if we have at least one model with enough samples to make dynamic ordering useful
121
+ const modelsWithEnoughSamples = healthData.filter(h => h.totalRequests >= this.config.minSamples);
122
+ // Use dynamic ordering if at least minSamples models have sufficient data
123
+ return modelsWithEnoughSamples.length >= this.config.minSamples;
124
+ }
125
+ /**
126
+ * Update configuration
127
+ */
128
+ updateConfig(newConfig) {
129
+ this.config = newConfig;
130
+ this.logger.debug('DynamicPrioritizer configuration updated', {
131
+ enabled: newConfig.enabled,
132
+ updateInterval: newConfig.updateInterval,
133
+ weights: {
134
+ successRate: newConfig.successRateWeight,
135
+ responseTime: newConfig.responseTimeWeight,
136
+ recentUsage: newConfig.recentUsageWeight,
137
+ },
138
+ });
139
+ // Update metrics when config changes
140
+ if (this.metricsManager) {
141
+ this.metricsManager.updateDynamicPrioritizationMetrics(newConfig.enabled, 0, // Reorders are tracked separately
142
+ this.modelScores.size);
143
+ }
144
+ // Clear scores when config changes
145
+ if (!newConfig.enabled) {
146
+ this.modelScores.clear();
147
+ }
148
+ }
149
+ /**
150
+ * Get current scores for all tracked models
151
+ */
152
+ getAllScores() {
153
+ return new Map(this.modelScores);
154
+ }
155
+ /**
156
+ * Check if dynamic prioritization is enabled
157
+ */
158
+ isEnabled() {
159
+ return this.config.enabled;
160
+ }
161
+ /**
162
+ * Get number of models with calculated scores
163
+ */
164
+ getModelsWithDynamicScores() {
165
+ return this.modelScores.size;
166
+ }
167
+ /**
168
+ * Update metrics with current dynamic prioritization state
169
+ */
170
+ updateMetrics() {
171
+ if (!this.metricsManager) {
172
+ return;
173
+ }
174
+ this.metricsManager.updateDynamicPrioritizationMetrics(this.config.enabled, 0, // Reorder count is cumulative, tracked separately
175
+ this.modelScores.size);
176
+ }
177
+ /**
178
+ * Reset all scores and usage history
179
+ */
180
+ reset() {
181
+ this.modelScores.clear();
182
+ this.modelUsageHistory.clear();
183
+ this.requestCount = 0;
184
+ }
185
+ /**
186
+ * Normalize response time (inverse - faster is better)
187
+ * Returns 0-1, higher is better
188
+ */
189
+ normalizeResponseTime(avgResponseTime) {
190
+ // Thresholds for normalization (in milliseconds)
191
+ const FAST_THRESHOLD = 500; // Below this is considered "fast"
192
+ const SLOW_THRESHOLD = 5000; // Above this is considered "slow"
193
+ if (avgResponseTime <= FAST_THRESHOLD) {
194
+ return 1.0; // Excellent
195
+ }
196
+ else if (avgResponseTime >= SLOW_THRESHOLD) {
197
+ return 0.1; // Poor (but not zero to allow for recovery)
198
+ }
199
+ else {
200
+ // Linear interpolation between thresholds
201
+ const ratio = (avgResponseTime - FAST_THRESHOLD) / (SLOW_THRESHOLD - FAST_THRESHOLD);
202
+ return 1.0 - (ratio * 0.9); // Scale from 1.0 to 0.1
203
+ }
204
+ }
205
+ /**
206
+ * Calculate recent usage score
207
+ * Returns 0-1, higher for more recent usage
208
+ */
209
+ calculateRecentUsageScore(key) {
210
+ const history = this.modelUsageHistory.get(key);
211
+ if (!history || history.length === 0) {
212
+ return 0.0;
213
+ }
214
+ const now = Date.now();
215
+ const lastUsage = history[history.length - 1];
216
+ // Time since last usage (in hours)
217
+ const timeSinceLastUsage = (now - lastUsage) / (1000 * 60 * 60);
218
+ // Decay score over time (24 hour window)
219
+ const decay = Math.max(0, 1 - (timeSinceLastUsage / 24));
220
+ // Bonus for frequent usage (more history entries = higher score)
221
+ const frequencyBonus = Math.min(1, history.length / 10);
222
+ // Combine decay and frequency (weighted towards recent activity)
223
+ return (decay * 0.7) + (frequencyBonus * 0.3);
224
+ }
225
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Confidence Scoring for Learned Patterns
3
+ */
4
+ import type { PatternCandidate, ErrorPattern, LearningConfig } from '../types/index.js';
5
+ /**
6
+ * ConfidenceScorer - Calculates confidence scores for learned patterns
7
+ */
8
+ export declare class ConfidenceScorer {
9
+ private config;
10
+ private knownPatterns;
11
+ constructor(config: LearningConfig, knownPatterns: ErrorPattern[]);
12
+ /**
13
+ * Calculate overall confidence score for a pattern
14
+ * @param pattern - The pattern candidate to score
15
+ * @param sampleCount - Number of times this pattern was seen
16
+ * @param learnedAt - Timestamp when pattern was first learned
17
+ * @returns Confidence score between 0 and 1
18
+ */
19
+ calculateScore(pattern: PatternCandidate, sampleCount: number, learnedAt?: number): number;
20
+ /**
21
+ * Calculate frequency score based on how often the pattern occurs
22
+ * @param count - Number of times pattern was seen
23
+ * @param window - Learning window size
24
+ * @returns Score between 0 and 1
25
+ */
26
+ calculateFrequencyScore(count: number, minFrequency: number): number;
27
+ /**
28
+ * Calculate similarity score based on how well pattern matches known patterns
29
+ * @param pattern - Pattern candidate to evaluate
30
+ * @returns Score between 0 and 1
31
+ */
32
+ calculateSimilarityScore(pattern: PatternCandidate): number;
33
+ /**
34
+ * Calculate recency score based on when pattern was first learned
35
+ * @param learnedAt - Timestamp when pattern was learned
36
+ * @returns Score between 0 and 1
37
+ */
38
+ calculateRecencyScore(learnedAt: number): number;
39
+ /**
40
+ * Check if pattern meets auto-approve threshold
41
+ * @param confidence - Confidence score
42
+ * @returns True if pattern should be auto-approved
43
+ */
44
+ shouldAutoApprove(confidence: number): boolean;
45
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Confidence Scoring for Learned Patterns
3
+ */
4
+ import { calculateJaccardSimilarity } from '../utils/similarity.js';
5
+ /**
6
+ * Weight for each confidence component
7
+ */
8
+ const FREQUENCY_WEIGHT = 0.5;
9
+ const SIMILARITY_WEIGHT = 0.3;
10
+ const RECENCY_WEIGHT = 0.2;
11
+ /**
12
+ * Common rate limit words for similarity calculation
13
+ */
14
+ const RATE_LIMIT_KEYWORDS = [
15
+ 'rate', 'limit', 'quota', 'exceeded', 'too', 'many', 'requests',
16
+ '429', '429', 'exhausted', 'resource', 'daily', 'monthly', 'maximum',
17
+ 'insufficient', 'per', 'minute', 'second', 'request',
18
+ ];
19
+ /**
20
+ * ConfidenceScorer - Calculates confidence scores for learned patterns
21
+ */
22
+ export class ConfidenceScorer {
23
+ config;
24
+ knownPatterns;
25
+ constructor(config, knownPatterns) {
26
+ this.config = config;
27
+ this.knownPatterns = knownPatterns;
28
+ }
29
+ /**
30
+ * Calculate overall confidence score for a pattern
31
+ * @param pattern - The pattern candidate to score
32
+ * @param sampleCount - Number of times this pattern was seen
33
+ * @param learnedAt - Timestamp when pattern was first learned
34
+ * @returns Confidence score between 0 and 1
35
+ */
36
+ calculateScore(pattern, sampleCount, learnedAt) {
37
+ const frequencyScore = this.calculateFrequencyScore(sampleCount, this.config.minErrorFrequency);
38
+ const similarityScore = this.calculateSimilarityScore(pattern);
39
+ const recencyScore = learnedAt ? this.calculateRecencyScore(learnedAt) : 0.5;
40
+ // Weighted average
41
+ const confidence = (frequencyScore * FREQUENCY_WEIGHT) +
42
+ (similarityScore * SIMILARITY_WEIGHT) +
43
+ (recencyScore * RECENCY_WEIGHT);
44
+ // Clamp to [0, 1]
45
+ return Math.max(0, Math.min(1, confidence));
46
+ }
47
+ /**
48
+ * Calculate frequency score based on how often the pattern occurs
49
+ * @param count - Number of times pattern was seen
50
+ * @param window - Learning window size
51
+ * @returns Score between 0 and 1
52
+ */
53
+ calculateFrequencyScore(count, minFrequency) {
54
+ if (count <= 0) {
55
+ return 0;
56
+ }
57
+ // Normalize against minimum frequency threshold
58
+ // Patterns seen at least minFrequency times get baseline score
59
+ // More frequent patterns get higher scores
60
+ const normalized = Math.min(count / (minFrequency * 2), 1);
61
+ // Boost score for patterns seen at least minFrequency times
62
+ const baseline = count >= minFrequency ? 0.5 : 0;
63
+ return Math.max(0, Math.min(1, baseline + normalized * 0.5));
64
+ }
65
+ /**
66
+ * Calculate similarity score based on how well pattern matches known patterns
67
+ * @param pattern - Pattern candidate to evaluate
68
+ * @returns Score between 0 and 1
69
+ */
70
+ calculateSimilarityScore(pattern) {
71
+ if (pattern.patterns.length === 0) {
72
+ return 0;
73
+ }
74
+ // Calculate keyword overlap with known rate limit patterns
75
+ const patternText = pattern.patterns.join(' ').toLowerCase();
76
+ const keywordsFound = RATE_LIMIT_KEYWORDS.filter(keyword => patternText.includes(keyword));
77
+ // Base score from keyword matching
78
+ const keywordScore = keywordsFound.length / RATE_LIMIT_KEYWORDS.length;
79
+ // Bonus for patterns that match known patterns
80
+ let knownPatternBonus = 0;
81
+ if (this.knownPatterns.length > 0) {
82
+ for (const known of this.knownPatterns) {
83
+ for (const knownPatternStr of known.patterns) {
84
+ const knownText = typeof knownPatternStr === 'string' ? knownPatternStr : knownPatternStr.source;
85
+ const similarity = calculateJaccardSimilarity(patternText, knownText.toLowerCase());
86
+ knownPatternBonus = Math.max(knownPatternBonus, similarity);
87
+ }
88
+ }
89
+ }
90
+ // Combine scores
91
+ return Math.max(0, Math.min(1, (keywordScore * 0.5) + (knownPatternBonus * 0.5)));
92
+ }
93
+ /**
94
+ * Calculate recency score based on when pattern was first learned
95
+ * @param learnedAt - Timestamp when pattern was learned
96
+ * @returns Score between 0 and 1
97
+ */
98
+ calculateRecencyScore(learnedAt) {
99
+ const now = Date.now();
100
+ const age = now - learnedAt;
101
+ // Learning window in milliseconds
102
+ const window = this.config.learningWindowMs;
103
+ // Recent patterns (within window) get higher scores
104
+ if (age <= window) {
105
+ return 1;
106
+ }
107
+ // Older patterns get decaying score
108
+ // Score decays over time, but never goes to 0
109
+ const decayFactor = Math.exp(-age / (window * 10));
110
+ return Math.max(0.3, decayFactor);
111
+ }
112
+ /**
113
+ * Check if pattern meets auto-approve threshold
114
+ * @param confidence - Confidence score
115
+ * @returns True if pattern should be auto-approved
116
+ */
117
+ shouldAutoApprove(confidence) {
118
+ return confidence >= this.config.autoApproveThreshold;
119
+ }
120
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Pattern Extraction from Rate Limit Errors
3
+ */
4
+ import type { PatternCandidate } from '../types/index.js';
5
+ /**
6
+ * PatternExtractor - Extracts pattern candidates from error messages
7
+ */
8
+ export declare class PatternExtractor {
9
+ /**
10
+ * Extract candidate patterns from an error
11
+ */
12
+ extractPatterns(error: unknown): PatternCandidate[];
13
+ /**
14
+ * Extract provider ID from error
15
+ */
16
+ extractProvider(error: unknown): string | null;
17
+ /**
18
+ * Extract HTTP status code from error
19
+ */
20
+ extractStatusCode(error: unknown): number | null;
21
+ /**
22
+ * Extract common rate limit phrases from error
23
+ */
24
+ extractPhrases(error: unknown): string[];
25
+ /**
26
+ * Validate if error is a valid object type
27
+ * @param error - The error to validate
28
+ * @returns True if error is a valid object
29
+ */
30
+ private isValidErrorObject;
31
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Pattern Extraction from Rate Limit Errors
3
+ */
4
+ /**
5
+ * Common rate limit phrases to extract from errors
6
+ */
7
+ const RATE_LIMIT_PHRASES = [
8
+ 'rate limit',
9
+ 'rate_limit',
10
+ 'ratelimit',
11
+ 'too many requests',
12
+ 'quota exceeded',
13
+ 'quota_exceeded',
14
+ 'insufficient_quota',
15
+ 'resource exhausted',
16
+ 'resource_exhausted',
17
+ 'daily limit exceeded',
18
+ 'monthly limit exceeded',
19
+ 'maximum requests',
20
+ 'requests per minute',
21
+ 'requests per second',
22
+ 'request limit',
23
+ 'request_limit',
24
+ 'limit exceeded',
25
+ 'limit_exceeded',
26
+ ];
27
+ /**
28
+ * Known provider identifiers
29
+ */
30
+ const KNOWN_PROVIDERS = ['anthropic', 'google', 'openai', 'azure', 'cohere', 'mistral', 'meta', 'huggingface', 'together'];
31
+ /**
32
+ * Rate limit status code regex patterns (pre-defined to avoid regex injection)
33
+ */
34
+ const RATE_LIMIT_STATUS_REGEX = {
35
+ 429: /\b429\b/gi,
36
+ 503: /\b503\b/gi,
37
+ };
38
+ /**
39
+ * PatternExtractor - Extracts pattern candidates from error messages
40
+ */
41
+ export class PatternExtractor {
42
+ /**
43
+ * Extract candidate patterns from an error
44
+ */
45
+ extractPatterns(error) {
46
+ if (!this.isValidErrorObject(error)) {
47
+ return [];
48
+ }
49
+ const err = error;
50
+ // Extract error text
51
+ const responseBody = String(err.data?.responseBody || '');
52
+ const message = String(err.data?.message || err.message || '');
53
+ const name = String(err.name || '');
54
+ const statusCode = err.data?.statusCode?.toString() || '';
55
+ const errorCode = err.data?.code?.toString() || err.data?.type?.toString() || '';
56
+ // Combine all text sources for original text
57
+ const originalText = [responseBody, message, name, statusCode, errorCode].join(' ');
58
+ // Extract patterns
59
+ const patterns = [];
60
+ // Extract HTTP status code
61
+ const extractedStatusCode = this.extractStatusCode(error);
62
+ if (extractedStatusCode) {
63
+ // Use pre-defined regex pattern instead of constructing one
64
+ if (extractedStatusCode in RATE_LIMIT_STATUS_REGEX) {
65
+ patterns.push(RATE_LIMIT_STATUS_REGEX[extractedStatusCode]);
66
+ }
67
+ patterns.push(String(extractedStatusCode));
68
+ }
69
+ // Extract provider
70
+ const provider = this.extractProvider(error);
71
+ // Extract common phrases
72
+ const extractedPhrases = this.extractPhrases(error);
73
+ patterns.push(...extractedPhrases);
74
+ // Extract error codes
75
+ if (errorCode) {
76
+ patterns.push(errorCode.toLowerCase());
77
+ }
78
+ // Filter out empty patterns
79
+ const validPatterns = patterns.filter(p => p && p !== '');
80
+ if (validPatterns.length === 0) {
81
+ return [];
82
+ }
83
+ return [{
84
+ provider: provider || undefined,
85
+ patterns: validPatterns,
86
+ sourceError: originalText,
87
+ extractedAt: Date.now(),
88
+ }];
89
+ }
90
+ /**
91
+ * Extract provider ID from error
92
+ */
93
+ extractProvider(error) {
94
+ if (!this.isValidErrorObject(error)) {
95
+ return null;
96
+ }
97
+ const err = error;
98
+ const allText = [
99
+ String(err.name || ''),
100
+ String(err.message || ''),
101
+ String(err.data?.message || ''),
102
+ String(err.data?.responseBody || ''),
103
+ ].join(' ').toLowerCase();
104
+ // Check for known provider names
105
+ for (const provider of KNOWN_PROVIDERS) {
106
+ if (allText.includes(provider)) {
107
+ return provider;
108
+ }
109
+ }
110
+ return null;
111
+ }
112
+ /**
113
+ * Extract HTTP status code from error
114
+ */
115
+ extractStatusCode(error) {
116
+ if (!this.isValidErrorObject(error)) {
117
+ return null;
118
+ }
119
+ const err = error;
120
+ const statusCode = err.data?.statusCode;
121
+ if (statusCode && typeof statusCode === 'number') {
122
+ return statusCode;
123
+ }
124
+ return null;
125
+ }
126
+ /**
127
+ * Extract common rate limit phrases from error
128
+ */
129
+ extractPhrases(error) {
130
+ if (!this.isValidErrorObject(error)) {
131
+ return [];
132
+ }
133
+ const err = error;
134
+ const allText = [
135
+ String(err.name || ''),
136
+ String(err.message || ''),
137
+ String(err.data?.message || ''),
138
+ String(err.data?.responseBody || ''),
139
+ ].join(' ').toLowerCase();
140
+ // Find matching phrases
141
+ const foundPhrases = [];
142
+ for (const phrase of RATE_LIMIT_PHRASES) {
143
+ if (allText.includes(phrase)) {
144
+ foundPhrases.push(phrase);
145
+ }
146
+ }
147
+ return foundPhrases;
148
+ }
149
+ /**
150
+ * Validate if error is a valid object type
151
+ * @param error - The error to validate
152
+ * @returns True if error is a valid object
153
+ */
154
+ isValidErrorObject(error) {
155
+ return error !== null && typeof error === 'object';
156
+ }
157
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Pattern Learning from Rate Limit Errors
3
+ */
4
+ import type { PatternCandidate, LearnedPattern, LearningConfig } from '../types/index.js';
5
+ import type { Logger } from '../../logger.js';
6
+ import { PatternExtractor } from './PatternExtractor.js';
7
+ import { ConfidenceScorer } from './ConfidenceScorer.js';
8
+ import { PatternStorage } from './PatternStorage.js';
9
+ /**
10
+ * PatternLearner - Orchestrates the learning process from errors
11
+ */
12
+ export declare class PatternLearner {
13
+ private extractor;
14
+ private scorer;
15
+ private storage;
16
+ private config;
17
+ private logger;
18
+ private patternTracker;
19
+ private learnedPatterns;
20
+ constructor(extractor: PatternExtractor, scorer: ConfidenceScorer, storage: PatternStorage, config: LearningConfig, logger: Logger);
21
+ /**
22
+ * Learn from a rate limit error
23
+ */
24
+ learnFromError(error: unknown): void;
25
+ /**
26
+ * Track a pattern candidate for learning
27
+ */
28
+ private trackPattern;
29
+ /**
30
+ * Process tracked patterns and save those meeting criteria
31
+ */
32
+ private processPatterns;
33
+ /**
34
+ * Merge similar patterns
35
+ */
36
+ mergePatterns(patterns: PatternCandidate[]): PatternCandidate | null;
37
+ /**
38
+ * Load learned patterns from storage
39
+ */
40
+ loadLearnedPatterns(): Promise<void>;
41
+ /**
42
+ * Get all learned patterns
43
+ */
44
+ getLearnedPatterns(): LearnedPattern[];
45
+ /**
46
+ * Get learned patterns for a specific provider
47
+ */
48
+ getLearnedPatternsForProvider(provider: string): LearnedPattern[];
49
+ /**
50
+ * Add a learned pattern manually
51
+ */
52
+ addLearnedPattern(pattern: LearnedPattern): Promise<void>;
53
+ /**
54
+ * Remove a learned pattern
55
+ */
56
+ removeLearnedPattern(name: string): Promise<boolean>;
57
+ /**
58
+ * Get a learned pattern by name
59
+ */
60
+ getLearnedPatternByName(name: string): LearnedPattern | undefined;
61
+ /**
62
+ * Generate a unique key for a pattern candidate
63
+ */
64
+ private generatePatternKey;
65
+ /**
66
+ * Generate a unique key for a learned pattern
67
+ */
68
+ private generatePatternKeyFromLearned;
69
+ /**
70
+ * Generate a name for a pattern
71
+ */
72
+ private generatePatternName;
73
+ /**
74
+ * Calculate priority for a learned pattern
75
+ */
76
+ private calculatePriority;
77
+ /**
78
+ * Merge duplicate patterns in storage
79
+ */
80
+ mergeDuplicatePatterns(): Promise<number>;
81
+ /**
82
+ * Cleanup old patterns
83
+ */
84
+ cleanupOldPatterns(): Promise<number>;
85
+ /**
86
+ * Clear all tracked patterns
87
+ */
88
+ clearTrackedPatterns(): void;
89
+ /**
90
+ * Get statistics about learning
91
+ */
92
+ getStats(): {
93
+ trackedPatterns: number;
94
+ learnedPatterns: number;
95
+ pendingPatterns: number;
96
+ };
97
+ }