@azumag/opencode-rate-limit-fallback 1.59.0 → 1.63.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.
@@ -1,157 +1,219 @@
1
1
  /**
2
- * Pattern Extraction from Rate Limit Errors
2
+ * Pattern Extractor for extracting patterns from error messages
3
3
  */
4
4
  /**
5
- * Common rate limit phrases to extract from errors
5
+ * Pre-defined provider IDs for matching
6
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',
7
+ const KNOWN_PROVIDERS = [
8
+ 'anthropic',
9
+ 'google',
10
+ 'openai',
11
+ 'cohere',
12
+ 'mistral',
13
+ 'together',
14
+ 'deepseek',
15
+ 'gemini',
26
16
  ];
27
17
  /**
28
- * Known provider identifiers
18
+ * Pre-defined HTTP status code regex patterns
29
19
  */
30
- const KNOWN_PROVIDERS = ['anthropic', 'google', 'openai', 'azure', 'cohere', 'mistral', 'meta', 'huggingface', 'together'];
20
+ const STATUS_CODE_PATTERNS = [
21
+ /\b(429|503|502|500)\b/g, // Common rate limit and server error codes
22
+ ];
23
+ /**
24
+ * Pre-defined rate limit phrase patterns
25
+ */
26
+ const RATE_LIMIT_PHRASE_PATTERNS = [
27
+ /(?:rate.?limit|quota|exceed|too.?many.?requests|throttl)/gi,
28
+ ];
31
29
  /**
32
- * Rate limit status code regex patterns (pre-defined to avoid regex injection)
30
+ * Pre-defined API error code patterns
33
31
  */
34
- const RATE_LIMIT_STATUS_REGEX = {
35
- 429: /\b429\b/gi,
36
- 503: /\b503\b/gi,
37
- };
32
+ const ERROR_CODE_PATTERNS = [
33
+ /\b(?:insufficient_quota|resource_exhausted|rate_limit_error|quota_exceeded|over_quota)\b/gi,
34
+ ];
38
35
  /**
39
- * PatternExtractor - Extracts pattern candidates from error messages
36
+ * Minimum length for pattern strings
37
+ */
38
+ const MIN_PATTERN_LENGTH = 3;
39
+ /**
40
+ * Pattern Extractor class
41
+ * Extracts pattern candidates from error messages
40
42
  */
41
43
  export class PatternExtractor {
42
44
  /**
43
- * Extract candidate patterns from an error
45
+ * Check if an object is a valid error object
46
+ */
47
+ isValidErrorObject(error) {
48
+ if (!error || typeof error !== 'object') {
49
+ return false;
50
+ }
51
+ return true;
52
+ }
53
+ /**
54
+ * Extract error text from various error fields
44
55
  */
45
- extractPatterns(error) {
56
+ extractErrorText(error) {
46
57
  if (!this.isValidErrorObject(error)) {
47
58
  return [];
48
59
  }
49
60
  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]);
61
+ const textSources = [];
62
+ // Extract from response body
63
+ if (err.data && typeof err.data === 'object') {
64
+ const data = err.data;
65
+ if (typeof data.responseBody === 'string') {
66
+ textSources.push(data.responseBody);
67
+ }
68
+ if (typeof data.message === 'string') {
69
+ textSources.push(data.message);
70
+ }
71
+ if (typeof data.statusCode === 'number') {
72
+ textSources.push(String(data.statusCode));
66
73
  }
67
- patterns.push(String(extractedStatusCode));
68
74
  }
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());
75
+ // Extract from error properties
76
+ if (typeof err.message === 'string') {
77
+ textSources.push(err.message);
77
78
  }
78
- // Filter out empty patterns
79
- const validPatterns = patterns.filter(p => p && p !== '');
80
- if (validPatterns.length === 0) {
81
- return [];
79
+ if (typeof err.name === 'string') {
80
+ textSources.push(err.name);
82
81
  }
83
- return [{
84
- provider: provider || undefined,
85
- patterns: validPatterns,
86
- sourceError: originalText,
87
- extractedAt: Date.now(),
88
- }];
82
+ return textSources;
89
83
  }
90
84
  /**
91
- * Extract provider ID from error
85
+ * Extract provider ID from error text
92
86
  */
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;
87
+ extractProvider(textSources) {
88
+ for (const text of textSources) {
89
+ const lowerText = text.toLowerCase();
90
+ for (const provider of KNOWN_PROVIDERS) {
91
+ if (lowerText.includes(provider)) {
92
+ return provider;
93
+ }
108
94
  }
109
95
  }
110
96
  return null;
111
97
  }
112
98
  /**
113
- * Extract HTTP status code from error
99
+ * Extract HTTP status codes from error text
114
100
  */
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;
101
+ extractStatusCodes(textSources) {
102
+ const statusCodes = new Set();
103
+ for (const text of textSources) {
104
+ for (const pattern of STATUS_CODE_PATTERNS) {
105
+ pattern.lastIndex = 0; // Reset regex state
106
+ const matches = text.matchAll(pattern);
107
+ for (const match of matches) {
108
+ if (match[1]) {
109
+ statusCodes.add(match[1]);
110
+ }
111
+ }
112
+ }
123
113
  }
124
- return null;
114
+ return Array.from(statusCodes);
125
115
  }
126
116
  /**
127
- * Extract common rate limit phrases from error
117
+ * Extract rate limit phrases from error text
128
118
  */
129
- extractPhrases(error) {
130
- if (!this.isValidErrorObject(error)) {
131
- return [];
119
+ extractPhrases(textSources) {
120
+ const phrases = new Set();
121
+ const lowerTextSources = textSources.map(t => t.toLowerCase());
122
+ // Common rate limit phrases to look for
123
+ const commonPhrases = [
124
+ 'rate limit',
125
+ 'rate_limit',
126
+ 'ratelimit',
127
+ 'too many requests',
128
+ 'quota exceeded',
129
+ 'rate limit exceeded',
130
+ 'quota limit',
131
+ 'insufficient quota',
132
+ 'rate limited',
133
+ 'rate-limited',
134
+ 'throttled',
135
+ 'resource exhausted',
136
+ 'daily limit',
137
+ 'request limit',
138
+ ];
139
+ for (const text of lowerTextSources) {
140
+ // Extract common phrases
141
+ for (const phrase of commonPhrases) {
142
+ if (text.includes(phrase)) {
143
+ phrases.add(phrase);
144
+ }
145
+ }
146
+ // Extract phrases using pre-defined patterns (for variations)
147
+ for (const pattern of RATE_LIMIT_PHRASE_PATTERNS) {
148
+ pattern.lastIndex = 0;
149
+ const matches = text.matchAll(pattern);
150
+ for (const match of matches) {
151
+ const phrase = match[0].toLowerCase().replace(/\s+/g, ' ').trim();
152
+ if (phrase.length >= MIN_PATTERN_LENGTH) {
153
+ phrases.add(phrase);
154
+ }
155
+ }
156
+ }
132
157
  }
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);
158
+ // Extract error codes (these go in errorCodes, not phrases)
159
+ // But we also add them to phrases for now
160
+ for (const text of lowerTextSources) {
161
+ for (const pattern of ERROR_CODE_PATTERNS) {
162
+ pattern.lastIndex = 0;
163
+ const matches = text.matchAll(pattern);
164
+ for (const match of matches) {
165
+ const errorCode = match[0].toLowerCase();
166
+ if (errorCode.length >= MIN_PATTERN_LENGTH) {
167
+ phrases.add(errorCode);
168
+ }
169
+ }
145
170
  }
146
171
  }
147
- return foundPhrases;
172
+ return Array.from(phrases);
148
173
  }
149
174
  /**
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
175
+ * Extract API error codes from error text
153
176
  */
154
- isValidErrorObject(error) {
155
- return error !== null && typeof error === 'object';
177
+ extractErrorCodes(textSources) {
178
+ const errorCodes = new Set();
179
+ for (const text of textSources) {
180
+ for (const pattern of ERROR_CODE_PATTERNS) {
181
+ pattern.lastIndex = 0;
182
+ const matches = text.matchAll(pattern);
183
+ for (const match of matches) {
184
+ const code = match[0].toLowerCase();
185
+ errorCodes.add(code);
186
+ }
187
+ }
188
+ }
189
+ return Array.from(errorCodes);
190
+ }
191
+ /**
192
+ * Extract pattern candidates from an error
193
+ */
194
+ extractPattern(error) {
195
+ if (!this.isValidErrorObject(error)) {
196
+ return null;
197
+ }
198
+ const textSources = this.extractErrorText(error);
199
+ if (textSources.length === 0) {
200
+ return null;
201
+ }
202
+ const provider = this.extractProvider(textSources);
203
+ const statusCodes = this.extractStatusCodes(textSources);
204
+ const phrases = this.extractPhrases(textSources);
205
+ const errorCodes = this.extractErrorCodes(textSources);
206
+ const rawText = textSources.join(' ').toLowerCase();
207
+ // If no patterns were extracted, return null
208
+ if (phrases.length === 0 && errorCodes.length === 0 && statusCodes.length === 0) {
209
+ return null;
210
+ }
211
+ return {
212
+ provider,
213
+ statusCode: statusCodes[0] || null,
214
+ phrases,
215
+ errorCodes,
216
+ rawText,
217
+ };
156
218
  }
157
219
  }
@@ -1,13 +1,11 @@
1
1
  /**
2
- * Pattern Learning from Rate Limit Errors
2
+ * Pattern Learner for orchestrating error pattern learning
3
3
  */
4
- import type { PatternCandidate, LearnedPattern, LearningConfig } from '../types/index.js';
4
+ import type { LearnedPattern, PatternLearningConfig } from '../types/index.js';
5
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
6
  /**
10
- * PatternLearner - Orchestrates the learning process from errors
7
+ * Pattern Learner class
8
+ * Orchestrates the learning process
11
9
  */
12
10
  export declare class PatternLearner {
13
11
  private extractor;
@@ -15,83 +13,54 @@ export declare class PatternLearner {
15
13
  private storage;
16
14
  private config;
17
15
  private logger;
18
- private patternTracker;
19
- private learnedPatterns;
20
- constructor(extractor: PatternExtractor, scorer: ConfidenceScorer, storage: PatternStorage, config: LearningConfig, logger: Logger);
16
+ private patternTracking;
17
+ private stats;
21
18
  /**
22
- * Learn from a rate limit error
19
+ * Constructor
23
20
  */
24
- learnFromError(error: unknown): void;
21
+ constructor(config: PatternLearningConfig, logger?: Logger);
25
22
  /**
26
- * Track a pattern candidate for learning
23
+ * Update configuration
27
24
  */
28
- private trackPattern;
25
+ updateConfig(config: PatternLearningConfig): void;
29
26
  /**
30
- * Process tracked patterns and save those meeting criteria
27
+ * Set the config file path for storage
31
28
  */
32
- private processPatterns;
29
+ setConfigFilePath(path: string): void;
33
30
  /**
34
- * Merge similar patterns
31
+ * Process an error and learn from it
35
32
  */
36
- mergePatterns(patterns: PatternCandidate[]): PatternCandidate | null;
33
+ processError(error: unknown): Promise<LearnedPattern | null>;
37
34
  /**
38
35
  * Load learned patterns from storage
39
36
  */
40
- loadLearnedPatterns(): Promise<void>;
37
+ loadLearnedPatterns(): Promise<LearnedPattern[]>;
41
38
  /**
42
- * Get all learned patterns
39
+ * Save learned patterns
43
40
  */
44
- getLearnedPatterns(): LearnedPattern[];
41
+ saveLearnedPatterns(patterns: LearnedPattern[]): Promise<void>;
45
42
  /**
46
- * Get learned patterns for a specific provider
43
+ * Get statistics
47
44
  */
48
- getLearnedPatternsForProvider(provider: string): LearnedPattern[];
45
+ getStats(): typeof PatternLearner.prototype.stats;
49
46
  /**
50
- * Add a learned pattern manually
47
+ * Reset statistics
51
48
  */
52
- addLearnedPattern(pattern: LearnedPattern): Promise<void>;
49
+ resetStats(): void;
53
50
  /**
54
- * Remove a learned pattern
51
+ * Clear all pattern tracking
55
52
  */
56
- removeLearnedPattern(name: string): Promise<boolean>;
53
+ clearTracking(): void;
57
54
  /**
58
- * Get a learned pattern by name
55
+ * Create a pattern key for tracking
59
56
  */
60
- getLearnedPatternByName(name: string): LearnedPattern | undefined;
57
+ private createPatternKey;
61
58
  /**
62
- * Generate a unique key for a pattern candidate
59
+ * Get or create pattern tracking
63
60
  */
64
- private generatePatternKey;
61
+ private getOrCreateTracking;
65
62
  /**
66
- * Generate a unique key for a learned pattern
63
+ * Save a single learned pattern
67
64
  */
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
- };
65
+ private saveLearnedPattern;
97
66
  }