@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 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
- * Destroy the circuit breaker and clean up resources
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
- * Destroy the circuit breaker and clean up resources
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
+ }