@azumag/opencode-rate-limit-fallback 1.35.0 → 1.36.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/dist/index.d.ts +1 -0
- package/dist/index.js +50 -5
- package/dist/src/circuitbreaker/CircuitBreaker.d.ts +8 -1
- package/dist/src/circuitbreaker/CircuitBreaker.js +11 -1
- package/dist/src/config/Validator.d.ts +64 -0
- package/dist/src/config/Validator.js +618 -0
- package/dist/src/diagnostics/Reporter.d.ts +128 -0
- package/dist/src/diagnostics/Reporter.js +285 -0
- package/dist/src/errors/PatternRegistry.d.ts +75 -0
- package/dist/src/errors/PatternRegistry.js +234 -0
- package/dist/src/fallback/FallbackHandler.d.ts +3 -1
- package/dist/src/fallback/FallbackHandler.js +16 -2
- package/dist/src/fallback/ModelSelector.d.ts +3 -1
- package/dist/src/fallback/ModelSelector.js +17 -1
- package/dist/src/health/HealthTracker.d.ts +96 -0
- package/dist/src/health/HealthTracker.js +353 -0
- package/dist/src/types/index.d.ts +52 -0
- package/package.json +1 -1
- package/dist/src/utils/errorDetection.d.ts +0 -7
- package/dist/src/utils/errorDetection.js +0 -34
package/dist/index.d.ts
CHANGED
|
@@ -8,3 +8,4 @@ export declare const RateLimitFallback: Plugin;
|
|
|
8
8
|
export default RateLimitFallback;
|
|
9
9
|
export type { PluginConfig, MetricsConfig, FallbackModel, FallbackMode, CircuitBreakerConfig, CircuitBreakerState, CircuitBreakerStateType } from "./src/types/index.js";
|
|
10
10
|
export type { LogConfig, Logger } from "./logger.js";
|
|
11
|
+
export { Logger as LoggerClass } from "./logger.js";
|
package/dist/index.js
CHANGED
|
@@ -7,9 +7,12 @@ import { createLogger } from "./logger.js";
|
|
|
7
7
|
import { MetricsManager } from "./src/metrics/MetricsManager.js";
|
|
8
8
|
import { FallbackHandler } from "./src/fallback/FallbackHandler.js";
|
|
9
9
|
import { loadConfig } from "./src/utils/config.js";
|
|
10
|
-
import { isRateLimitError } from "./src/utils/errorDetection.js";
|
|
11
10
|
import { SubagentTracker } from "./src/session/SubagentTracker.js";
|
|
12
11
|
import { CLEANUP_INTERVAL_MS } from "./src/types/index.js";
|
|
12
|
+
import { ConfigValidator } from "./src/config/Validator.js";
|
|
13
|
+
import { ErrorPatternRegistry } from "./src/errors/PatternRegistry.js";
|
|
14
|
+
import { HealthTracker } from "./src/health/HealthTracker.js";
|
|
15
|
+
import { DiagnosticReporter } from "./src/diagnostics/Reporter.js";
|
|
13
16
|
// ============================================================================
|
|
14
17
|
// Event Type Guards
|
|
15
18
|
// ============================================================================
|
|
@@ -70,38 +73,76 @@ export const RateLimitFallback = async ({ client, directory, worktree }) => {
|
|
|
70
73
|
else {
|
|
71
74
|
logger.info("No config file found, using defaults");
|
|
72
75
|
}
|
|
76
|
+
// Initialize configuration validator
|
|
77
|
+
const validator = new ConfigValidator(logger);
|
|
78
|
+
const validation = configSource
|
|
79
|
+
? validator.validateFile(configSource, config.configValidation)
|
|
80
|
+
: validator.validate(config, config.configValidation);
|
|
81
|
+
if (!validation.isValid && config.configValidation?.strict) {
|
|
82
|
+
logger.error("Configuration validation failed in strict mode. Plugin will not load.");
|
|
83
|
+
logger.error(`Errors: ${validation.errors.map(e => `${e.path}: ${e.message}`).join(', ')}`);
|
|
84
|
+
return {};
|
|
85
|
+
}
|
|
86
|
+
if (validation.errors.length > 0) {
|
|
87
|
+
logger.warn(`Configuration validation found ${validation.errors.length} error(s)`);
|
|
88
|
+
}
|
|
89
|
+
if (validation.warnings.length > 0) {
|
|
90
|
+
logger.warn(`Configuration validation found ${validation.warnings.length} warning(s)`);
|
|
91
|
+
}
|
|
73
92
|
if (!config.enabled) {
|
|
74
93
|
return {};
|
|
75
94
|
}
|
|
95
|
+
// Initialize error pattern registry
|
|
96
|
+
const errorPatternRegistry = new ErrorPatternRegistry(logger);
|
|
97
|
+
if (config.errorPatterns?.custom) {
|
|
98
|
+
errorPatternRegistry.registerMany(config.errorPatterns.custom);
|
|
99
|
+
}
|
|
100
|
+
// Initialize health tracker
|
|
101
|
+
let healthTracker;
|
|
102
|
+
if (config.enableHealthBasedSelection) {
|
|
103
|
+
healthTracker = new HealthTracker(config, logger);
|
|
104
|
+
logger.info("Health-based model selection enabled");
|
|
105
|
+
}
|
|
106
|
+
// Initialize diagnostic reporter
|
|
107
|
+
const diagnostics = new DiagnosticReporter(config, configSource || 'default', healthTracker, undefined, // circuitBreaker will be initialized in FallbackHandler
|
|
108
|
+
errorPatternRegistry, logger);
|
|
109
|
+
// Log startup diagnostics if verbose mode
|
|
110
|
+
if (config.verbose) {
|
|
111
|
+
logger.debug("Verbose mode enabled - showing diagnostic information");
|
|
112
|
+
diagnostics.logCurrentConfig();
|
|
113
|
+
}
|
|
76
114
|
// Initialize components
|
|
77
115
|
const subagentTracker = new SubagentTracker(config);
|
|
78
116
|
const metricsManager = new MetricsManager(config.metrics ?? { enabled: false, output: { console: true, format: "pretty" }, resetInterval: "daily" }, logger);
|
|
79
|
-
const fallbackHandler = new FallbackHandler(config, client, logger, metricsManager, subagentTracker);
|
|
117
|
+
const fallbackHandler = new FallbackHandler(config, client, logger, metricsManager, subagentTracker, healthTracker);
|
|
80
118
|
// Cleanup stale entries periodically
|
|
81
119
|
const cleanupInterval = setInterval(() => {
|
|
82
120
|
subagentTracker.cleanupStaleEntries();
|
|
83
121
|
fallbackHandler.cleanupStaleEntries();
|
|
122
|
+
if (healthTracker) {
|
|
123
|
+
healthTracker.cleanupOldEntries();
|
|
124
|
+
}
|
|
84
125
|
}, CLEANUP_INTERVAL_MS);
|
|
85
126
|
return {
|
|
86
127
|
event: async ({ event }) => {
|
|
87
128
|
// Handle session.error events
|
|
88
129
|
if (isSessionErrorEvent(event)) {
|
|
89
130
|
const { sessionID, error } = event.properties;
|
|
90
|
-
if (sessionID && error && isRateLimitError(error)) {
|
|
131
|
+
if (sessionID && error && errorPatternRegistry.isRateLimitError(error)) {
|
|
91
132
|
await fallbackHandler.handleRateLimitFallback(sessionID, "", "");
|
|
92
133
|
}
|
|
93
134
|
}
|
|
94
135
|
// Handle message.updated events
|
|
95
136
|
if (isMessageUpdatedEvent(event)) {
|
|
96
137
|
const info = event.properties.info;
|
|
97
|
-
if (info?.error && isRateLimitError(info.error)) {
|
|
138
|
+
if (info?.error && errorPatternRegistry.isRateLimitError(info.error)) {
|
|
98
139
|
await fallbackHandler.handleRateLimitFallback(info.sessionID, info.providerID || "", info.modelID || "");
|
|
99
140
|
}
|
|
100
141
|
else if (info?.status === "completed" && !info?.error && info?.id) {
|
|
101
142
|
// Record fallback success
|
|
102
143
|
fallbackHandler.handleMessageUpdated(info.sessionID, info.id, false, false);
|
|
103
144
|
}
|
|
104
|
-
else if (info?.error && !isRateLimitError(info.error) && info?.id) {
|
|
145
|
+
else if (info?.error && !errorPatternRegistry.isRateLimitError(info.error) && info?.id) {
|
|
105
146
|
// Record non-rate-limit error
|
|
106
147
|
fallbackHandler.handleMessageUpdated(info.sessionID, info.id, true, false);
|
|
107
148
|
}
|
|
@@ -137,7 +178,11 @@ export const RateLimitFallback = async ({ client, directory, worktree }) => {
|
|
|
137
178
|
subagentTracker.clearAll();
|
|
138
179
|
metricsManager.destroy();
|
|
139
180
|
fallbackHandler.destroy();
|
|
181
|
+
if (healthTracker) {
|
|
182
|
+
healthTracker.destroy();
|
|
183
|
+
}
|
|
140
184
|
},
|
|
141
185
|
};
|
|
142
186
|
};
|
|
143
187
|
export default RateLimitFallback;
|
|
188
|
+
export { Logger as LoggerClass } from "./logger.js";
|
|
@@ -47,7 +47,14 @@ export declare class CircuitBreaker {
|
|
|
47
47
|
*/
|
|
48
48
|
private getOrCreateCircuit;
|
|
49
49
|
/**
|
|
50
|
-
*
|
|
50
|
+
* Get all circuit states
|
|
51
|
+
*/
|
|
52
|
+
getAllStates(): {
|
|
53
|
+
modelKey: string;
|
|
54
|
+
state: CircuitBreakerState;
|
|
55
|
+
}[];
|
|
56
|
+
/**
|
|
57
|
+
* Destroy circuit breaker and clean up resources
|
|
51
58
|
*/
|
|
52
59
|
destroy(): void;
|
|
53
60
|
}
|
|
@@ -199,7 +199,17 @@ export class CircuitBreaker {
|
|
|
199
199
|
return circuit;
|
|
200
200
|
}
|
|
201
201
|
/**
|
|
202
|
-
*
|
|
202
|
+
* Get all circuit states
|
|
203
|
+
*/
|
|
204
|
+
getAllStates() {
|
|
205
|
+
const result = [];
|
|
206
|
+
for (const [modelKey, circuit] of this.circuits.entries()) {
|
|
207
|
+
result.push({ modelKey, state: circuit.getState() });
|
|
208
|
+
}
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Destroy circuit breaker and clean up resources
|
|
203
213
|
*/
|
|
204
214
|
destroy() {
|
|
205
215
|
this.circuits.clear();
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration validation and diagnostics
|
|
3
|
+
*/
|
|
4
|
+
import type { PluginConfig } from '../types/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Validation error details
|
|
7
|
+
*/
|
|
8
|
+
export interface ValidationError {
|
|
9
|
+
path: string;
|
|
10
|
+
message: string;
|
|
11
|
+
severity: 'error' | 'warning';
|
|
12
|
+
value?: unknown;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Validation result
|
|
16
|
+
*/
|
|
17
|
+
export interface ValidationResult {
|
|
18
|
+
isValid: boolean;
|
|
19
|
+
errors: ValidationError[];
|
|
20
|
+
warnings: ValidationError[];
|
|
21
|
+
config: PluginConfig;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Validation configuration
|
|
25
|
+
*/
|
|
26
|
+
export interface ConfigValidationOptions {
|
|
27
|
+
strict?: boolean;
|
|
28
|
+
logWarnings?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Diagnostics configuration
|
|
32
|
+
*/
|
|
33
|
+
export interface DiagnosticsInfo {
|
|
34
|
+
configSource: string;
|
|
35
|
+
config: PluginConfig;
|
|
36
|
+
validation: ValidationResult;
|
|
37
|
+
defaultsApplied: string[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Configuration Validator class
|
|
41
|
+
*/
|
|
42
|
+
export declare class ConfigValidator {
|
|
43
|
+
private logger?;
|
|
44
|
+
constructor(logger?: {
|
|
45
|
+
warn: (msg: string) => void;
|
|
46
|
+
error: (msg: string) => void;
|
|
47
|
+
});
|
|
48
|
+
/**
|
|
49
|
+
* Validate a configuration object
|
|
50
|
+
*/
|
|
51
|
+
validate(config: Partial<PluginConfig>, options?: ConfigValidationOptions): ValidationResult;
|
|
52
|
+
/**
|
|
53
|
+
* Validate a configuration file
|
|
54
|
+
*/
|
|
55
|
+
validateFile(filePath: string, options?: ConfigValidationOptions): ValidationResult;
|
|
56
|
+
/**
|
|
57
|
+
* Get diagnostics information for the current configuration
|
|
58
|
+
*/
|
|
59
|
+
getDiagnostics(config: PluginConfig, configSource: string, defaultsApplied?: string[]): DiagnosticsInfo;
|
|
60
|
+
/**
|
|
61
|
+
* Format diagnostics as human-readable text
|
|
62
|
+
*/
|
|
63
|
+
formatDiagnostics(diagnostics: DiagnosticsInfo): string;
|
|
64
|
+
}
|