@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.
- package/README.md +219 -36
- package/dist/src/config/Validator.js +94 -0
- package/dist/src/config/defaults.d.ts +22 -0
- package/dist/src/config/defaults.js +28 -0
- package/dist/src/dynamic/DynamicPrioritizer.d.ts +74 -0
- package/dist/src/dynamic/DynamicPrioritizer.js +225 -0
- package/dist/src/errors/ConfidenceScorer.d.ts +45 -0
- package/dist/src/errors/ConfidenceScorer.js +120 -0
- package/dist/src/errors/PatternExtractor.d.ts +31 -0
- package/dist/src/errors/PatternExtractor.js +157 -0
- package/dist/src/errors/PatternLearner.d.ts +97 -0
- package/dist/src/errors/PatternLearner.js +257 -0
- package/dist/src/errors/PatternRegistry.d.ts +54 -5
- package/dist/src/errors/PatternRegistry.js +171 -8
- package/dist/src/errors/PatternStorage.d.ts +49 -0
- package/dist/src/errors/PatternStorage.js +234 -0
- package/dist/src/fallback/FallbackHandler.d.ts +3 -2
- package/dist/src/fallback/FallbackHandler.js +38 -5
- package/dist/src/fallback/ModelSelector.d.ts +7 -1
- package/dist/src/fallback/ModelSelector.js +14 -1
- package/dist/src/main/ConfigReloader.d.ts +8 -1
- package/dist/src/main/ConfigReloader.js +45 -1
- package/dist/src/metrics/MetricsManager.d.ts +8 -0
- package/dist/src/metrics/MetricsManager.js +45 -0
- package/dist/src/types/index.d.ts +55 -0
- package/dist/src/utils/config.js +49 -6
- package/dist/src/utils/similarity.d.ts +10 -0
- package/dist/src/utils/similarity.js +24 -0
- package/package.json +1 -1
|
@@ -113,6 +113,26 @@ export interface HealthTrackerConfig {
|
|
|
113
113
|
* Use this for backward compatibility.
|
|
114
114
|
*/
|
|
115
115
|
export type HealthPersistenceConfig = HealthTrackerConfig;
|
|
116
|
+
/**
|
|
117
|
+
* Dynamic prioritization configuration
|
|
118
|
+
*/
|
|
119
|
+
export interface DynamicPrioritizationConfig {
|
|
120
|
+
enabled: boolean;
|
|
121
|
+
updateInterval: number;
|
|
122
|
+
successRateWeight: number;
|
|
123
|
+
responseTimeWeight: number;
|
|
124
|
+
recentUsageWeight: number;
|
|
125
|
+
minSamples: number;
|
|
126
|
+
maxHistorySize: number;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Dynamic prioritization metrics
|
|
130
|
+
*/
|
|
131
|
+
export interface DynamicPrioritizationMetrics {
|
|
132
|
+
enabled: boolean;
|
|
133
|
+
reorders: number;
|
|
134
|
+
modelsWithDynamicScores: number;
|
|
135
|
+
}
|
|
116
136
|
/**
|
|
117
137
|
* Health metrics for a model
|
|
118
138
|
*/
|
|
@@ -139,12 +159,45 @@ export interface ErrorPattern {
|
|
|
139
159
|
patterns: (string | RegExp)[];
|
|
140
160
|
priority: number;
|
|
141
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Pattern candidate extracted from an error
|
|
164
|
+
*/
|
|
165
|
+
export interface PatternCandidate {
|
|
166
|
+
provider?: string;
|
|
167
|
+
patterns: (string | RegExp)[];
|
|
168
|
+
sourceError: string;
|
|
169
|
+
extractedAt: number;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Learned pattern with metadata
|
|
173
|
+
*/
|
|
174
|
+
export interface LearnedPattern extends ErrorPattern {
|
|
175
|
+
confidence: number;
|
|
176
|
+
learnedAt: string;
|
|
177
|
+
sampleCount: number;
|
|
178
|
+
lastUsed?: number;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Learning configuration
|
|
182
|
+
*/
|
|
183
|
+
export interface LearningConfig {
|
|
184
|
+
enabled: boolean;
|
|
185
|
+
autoApproveThreshold: number;
|
|
186
|
+
maxLearnedPatterns: number;
|
|
187
|
+
minErrorFrequency: number;
|
|
188
|
+
learningWindowMs: number;
|
|
189
|
+
}
|
|
142
190
|
/**
|
|
143
191
|
* Error pattern configuration
|
|
144
192
|
*/
|
|
145
193
|
export interface ErrorPatternsConfig {
|
|
146
194
|
custom?: ErrorPattern[];
|
|
147
195
|
enableLearning?: boolean;
|
|
196
|
+
learnedPatterns?: LearnedPattern[];
|
|
197
|
+
autoApproveThreshold?: number;
|
|
198
|
+
maxLearnedPatterns?: number;
|
|
199
|
+
minErrorFrequency?: number;
|
|
200
|
+
learningWindowMs?: number;
|
|
148
201
|
}
|
|
149
202
|
/**
|
|
150
203
|
* Configuration hot reload settings
|
|
@@ -193,6 +246,7 @@ export interface PluginConfig {
|
|
|
193
246
|
verbose?: boolean;
|
|
194
247
|
errorPatterns?: ErrorPatternsConfig;
|
|
195
248
|
configReload?: ConfigReloadConfig;
|
|
249
|
+
dynamicPrioritization?: DynamicPrioritizationConfig;
|
|
196
250
|
}
|
|
197
251
|
/**
|
|
198
252
|
* Fallback state for tracking progress
|
|
@@ -357,6 +411,7 @@ export interface MetricsData {
|
|
|
357
411
|
total: CircuitBreakerMetrics;
|
|
358
412
|
byModel: Map<string, CircuitBreakerMetrics>;
|
|
359
413
|
};
|
|
414
|
+
dynamicPrioritization: DynamicPrioritizationMetrics;
|
|
360
415
|
startedAt: number;
|
|
361
416
|
generatedAt: number;
|
|
362
417
|
}
|
package/dist/src/utils/config.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Configuration loading and validation
|
|
3
3
|
*/
|
|
4
4
|
import { existsSync, readFileSync } from "fs";
|
|
5
|
-
import { join } from "path";
|
|
5
|
+
import { join, resolve, normalize, relative } from "path";
|
|
6
6
|
import { DEFAULT_FALLBACK_MODELS, VALID_FALLBACK_MODES, VALID_RESET_INTERVALS, DEFAULT_RETRY_POLICY, VALID_RETRY_STRATEGIES, DEFAULT_CIRCUIT_BREAKER_CONFIG, } from '../types/index.js';
|
|
7
|
-
import { DEFAULT_HEALTH_TRACKER_CONFIG, DEFAULT_COOLDOWN_MS, DEFAULT_FALLBACK_MODE, DEFAULT_LOG_CONFIG, DEFAULT_METRICS_CONFIG, DEFAULT_CONFIG_RELOAD_CONFIG, } from '../config/defaults.js';
|
|
7
|
+
import { DEFAULT_HEALTH_TRACKER_CONFIG, DEFAULT_COOLDOWN_MS, DEFAULT_FALLBACK_MODE, DEFAULT_LOG_CONFIG, DEFAULT_METRICS_CONFIG, DEFAULT_CONFIG_RELOAD_CONFIG, DEFAULT_DYNAMIC_PRIORITIZATION_CONFIG, DEFAULT_ERROR_PATTERN_LEARNING_CONFIG, } from '../config/defaults.js';
|
|
8
8
|
/**
|
|
9
9
|
* Default plugin configuration
|
|
10
10
|
*/
|
|
@@ -19,7 +19,35 @@ export const DEFAULT_CONFIG = {
|
|
|
19
19
|
log: DEFAULT_LOG_CONFIG,
|
|
20
20
|
metrics: DEFAULT_METRICS_CONFIG,
|
|
21
21
|
configReload: DEFAULT_CONFIG_RELOAD_CONFIG,
|
|
22
|
+
dynamicPrioritization: DEFAULT_DYNAMIC_PRIORITIZATION_CONFIG,
|
|
23
|
+
errorPatterns: DEFAULT_ERROR_PATTERN_LEARNING_CONFIG,
|
|
22
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* Validate that a path does not contain directory traversal attempts
|
|
27
|
+
*/
|
|
28
|
+
function validatePathSafety(path, allowedDirs) {
|
|
29
|
+
try {
|
|
30
|
+
const resolvedPath = resolve(path);
|
|
31
|
+
const normalizedPath = normalize(path);
|
|
32
|
+
// Check for obvious path traversal patterns
|
|
33
|
+
if (normalizedPath.includes('..')) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
// Check that resolved path is within allowed directories
|
|
37
|
+
for (const allowedDir of allowedDirs) {
|
|
38
|
+
const resolvedAllowedDir = resolve(allowedDir);
|
|
39
|
+
const relativePath = relative(resolvedAllowedDir, resolvedPath);
|
|
40
|
+
// If relative path does not start with '..', the path is within the allowed directory
|
|
41
|
+
if (!relativePath.startsWith('..')) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
23
51
|
/**
|
|
24
52
|
* Validate configuration values
|
|
25
53
|
*/
|
|
@@ -59,6 +87,14 @@ export function validateConfig(config) {
|
|
|
59
87
|
...DEFAULT_CONFIG.configReload,
|
|
60
88
|
...config.configReload,
|
|
61
89
|
} : DEFAULT_CONFIG.configReload,
|
|
90
|
+
dynamicPrioritization: config.dynamicPrioritization ? {
|
|
91
|
+
...DEFAULT_DYNAMIC_PRIORITIZATION_CONFIG,
|
|
92
|
+
...config.dynamicPrioritization,
|
|
93
|
+
} : DEFAULT_DYNAMIC_PRIORITIZATION_CONFIG,
|
|
94
|
+
errorPatterns: config.errorPatterns ? {
|
|
95
|
+
...DEFAULT_ERROR_PATTERN_LEARNING_CONFIG,
|
|
96
|
+
...config.errorPatterns,
|
|
97
|
+
} : DEFAULT_ERROR_PATTERN_LEARNING_CONFIG,
|
|
62
98
|
};
|
|
63
99
|
}
|
|
64
100
|
/**
|
|
@@ -70,18 +106,18 @@ export function loadConfig(directory, worktree, logger) {
|
|
|
70
106
|
// Build search paths: worktree first, then directory, then home locations
|
|
71
107
|
const searchDirs = [];
|
|
72
108
|
if (worktree) {
|
|
73
|
-
searchDirs.push(worktree);
|
|
109
|
+
searchDirs.push(resolve(worktree));
|
|
74
110
|
}
|
|
75
111
|
if (!worktree || worktree !== directory) {
|
|
76
|
-
searchDirs.push(directory);
|
|
112
|
+
searchDirs.push(resolve(directory));
|
|
77
113
|
}
|
|
114
|
+
searchDirs.push(resolve(homedir));
|
|
115
|
+
searchDirs.push(resolve(xdgConfigHome));
|
|
78
116
|
const configPaths = [];
|
|
79
117
|
for (const dir of searchDirs) {
|
|
80
118
|
configPaths.push(join(dir, ".opencode", "rate-limit-fallback.json"));
|
|
81
119
|
configPaths.push(join(dir, "rate-limit-fallback.json"));
|
|
82
120
|
}
|
|
83
|
-
configPaths.push(join(homedir, ".opencode", "rate-limit-fallback.json"));
|
|
84
|
-
configPaths.push(join(xdgConfigHome, "opencode", "rate-limit-fallback.json"));
|
|
85
121
|
// Log search paths for debugging
|
|
86
122
|
if (logger) {
|
|
87
123
|
logger.debug(`Searching for config file in ${configPaths.length} locations`);
|
|
@@ -92,6 +128,13 @@ export function loadConfig(directory, worktree, logger) {
|
|
|
92
128
|
}
|
|
93
129
|
for (const configPath of configPaths) {
|
|
94
130
|
if (existsSync(configPath)) {
|
|
131
|
+
// Validate path safety before reading
|
|
132
|
+
if (!validatePathSafety(configPath, searchDirs)) {
|
|
133
|
+
if (logger) {
|
|
134
|
+
logger.warn(`Config file rejected due to path validation: ${configPath}`);
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
95
138
|
try {
|
|
96
139
|
const content = readFileSync(configPath, "utf-8");
|
|
97
140
|
const userConfig = JSON.parse(content);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Similarity utility functions
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Calculate Jaccard similarity between two strings
|
|
6
|
+
* @param str1 - First string
|
|
7
|
+
* @param str2 - Second string
|
|
8
|
+
* @returns Similarity score between 0 and 1
|
|
9
|
+
*/
|
|
10
|
+
export declare function calculateJaccardSimilarity(str1: string, str2: string): number;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Similarity utility functions
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Calculate Jaccard similarity between two strings
|
|
6
|
+
* @param str1 - First string
|
|
7
|
+
* @param str2 - Second string
|
|
8
|
+
* @returns Similarity score between 0 and 1
|
|
9
|
+
*/
|
|
10
|
+
export function calculateJaccardSimilarity(str1, str2) {
|
|
11
|
+
// Tokenize strings
|
|
12
|
+
const tokens1 = new Set(str1.split(/\s+/).filter(t => t.length > 0));
|
|
13
|
+
const tokens2 = new Set(str2.split(/\s+/).filter(t => t.length > 0));
|
|
14
|
+
if (tokens1.size === 0 && tokens2.size === 0) {
|
|
15
|
+
return 1;
|
|
16
|
+
}
|
|
17
|
+
if (tokens1.size === 0 || tokens2.size === 0) {
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
// Calculate intersection and union
|
|
21
|
+
const intersection = new Set([...tokens1].filter(x => tokens2.has(x)));
|
|
22
|
+
const union = new Set([...tokens1, ...tokens2]);
|
|
23
|
+
return intersection.size / union.size;
|
|
24
|
+
}
|
package/package.json
CHANGED