@umituz/react-native-ai-gemini-provider 2.1.5 → 2.1.6
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/package.json +1 -1
- package/src/index.ts +11 -104
- package/src/infrastructure/interceptors/BaseInterceptor.ts +78 -0
- package/src/infrastructure/interceptors/RequestInterceptors.ts +6 -62
- package/src/infrastructure/interceptors/ResponseInterceptors.ts +6 -61
- package/src/infrastructure/interceptors/index.ts +7 -13
- package/src/infrastructure/services/base-gemini.service.ts +82 -0
- package/src/infrastructure/services/gemini-streaming.service.ts +31 -61
- package/src/infrastructure/services/gemini-text-generation.service.ts +11 -33
- package/src/infrastructure/services/index.ts +7 -4
- package/src/infrastructure/utils/async/index.ts +1 -10
- package/src/infrastructure/utils/index.ts +43 -26
- package/src/infrastructure/utils/stream-processor.util.ts +156 -0
- package/src/infrastructure/utils/validation-composer.util.ts +160 -0
- package/src/infrastructure/utils/validation.util.ts +9 -68
- package/src/presentation/hooks/index.ts +6 -1
- package/src/presentation/hooks/use-gemini.ts +21 -72
- package/src/presentation/hooks/use-operation-manager.ts +88 -0
- package/src/providers/ConfigBuilder.ts +121 -0
- package/src/providers/ProviderFactory.ts +37 -49
- package/src/providers/index.ts +4 -13
- package/src/infrastructure/utils/async/debounce.util.ts +0 -100
- package/src/infrastructure/utils/async/memoize.util.ts +0 -55
- package/src/infrastructure/utils/env.util.ts +0 -175
- package/src/infrastructure/utils/performance.util.ts +0 -139
- package/src/infrastructure/utils/rate-limiter.util.ts +0 -86
- package/src/infrastructure/utils/retry.util.ts +0 -158
- package/src/providers/ProviderConfig.ts +0 -36
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Environment Configuration Utilities
|
|
3
|
-
* Safe loading and validation of environment variables
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Get environment variable with type safety
|
|
8
|
-
*/
|
|
9
|
-
function getEnvVar(key: string): string | undefined {
|
|
10
|
-
// Check for React Native environment
|
|
11
|
-
if (typeof process !== "undefined" && process.env) {
|
|
12
|
-
return process.env[key];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// For React Native, apps typically use a config file
|
|
16
|
-
// This returns undefined if not available - app should provide config directly
|
|
17
|
-
return undefined;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Get required environment variable
|
|
22
|
-
* @throws Error if variable is not set
|
|
23
|
-
*/
|
|
24
|
-
export function getRequiredEnv(key: string): string {
|
|
25
|
-
const value = getEnvVar(key);
|
|
26
|
-
|
|
27
|
-
if (!value || value.trim().length === 0) {
|
|
28
|
-
throw new Error(`Required environment variable "${key}" is not set`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return value.trim();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Get optional environment variable with fallback
|
|
36
|
-
*/
|
|
37
|
-
export function getOptionalEnv(
|
|
38
|
-
key: string,
|
|
39
|
-
fallback: string,
|
|
40
|
-
): string {
|
|
41
|
-
const value = getEnvVar(key);
|
|
42
|
-
return value?.trim() || fallback;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Get environment variable as number
|
|
47
|
-
*/
|
|
48
|
-
export function getEnvNumber(key: string, fallback: number): number {
|
|
49
|
-
const value = getEnvVar(key);
|
|
50
|
-
|
|
51
|
-
if (!value) {
|
|
52
|
-
return fallback;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const parsed = parseInt(value, 10);
|
|
56
|
-
|
|
57
|
-
if (isNaN(parsed)) {
|
|
58
|
-
return fallback;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return parsed;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Get environment variable as boolean
|
|
66
|
-
*/
|
|
67
|
-
export function getEnvBoolean(key: string, fallback: boolean): boolean {
|
|
68
|
-
const value = getEnvVar(key);
|
|
69
|
-
|
|
70
|
-
if (!value) {
|
|
71
|
-
return fallback;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return value.toLowerCase() === "true" || value === "1";
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Environment configuration interface
|
|
79
|
-
*/
|
|
80
|
-
export interface EnvConfig {
|
|
81
|
-
/** Gemini API key */
|
|
82
|
-
GEMINI_API_KEY?: string;
|
|
83
|
-
/** Default timeout in milliseconds */
|
|
84
|
-
GEMINI_TIMEOUT?: number;
|
|
85
|
-
/** Enable debug logging */
|
|
86
|
-
GEMINI_DEBUG?: boolean;
|
|
87
|
-
/** Max retry attempts */
|
|
88
|
-
GEMINI_MAX_RETRIES?: number;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Load all Gemini-related environment variables
|
|
93
|
-
*/
|
|
94
|
-
export function loadGeminiEnv(): EnvConfig {
|
|
95
|
-
return {
|
|
96
|
-
GEMINI_API_KEY: getEnvVar("GEMINI_API_KEY"),
|
|
97
|
-
GEMINI_TIMEOUT: getEnvNumber("GEMINI_TIMEOUT", 30000),
|
|
98
|
-
GEMINI_DEBUG: getEnvBoolean("GEMINI_DEBUG", false),
|
|
99
|
-
GEMINI_MAX_RETRIES: getEnvNumber("GEMINI_MAX_RETRIES", 3),
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Get API key from environment with validation
|
|
105
|
-
* @throws Error if API key is not set or invalid
|
|
106
|
-
*/
|
|
107
|
-
export function getApiKeyFromEnv(): string {
|
|
108
|
-
const apiKey = getRequiredEnv("GEMINI_API_KEY");
|
|
109
|
-
|
|
110
|
-
if (apiKey.length < 10) {
|
|
111
|
-
throw new Error("GEMINI_API_KEY appears to be invalid (too short)");
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return apiKey;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Check if running in development mode
|
|
119
|
-
*/
|
|
120
|
-
export function isDevelopment(): boolean {
|
|
121
|
-
return getEnvBoolean("NODE_ENV", false) === false ||
|
|
122
|
-
getEnvVar("NODE_ENV") === "development";
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Check if debug mode is enabled
|
|
127
|
-
*/
|
|
128
|
-
export function isDebugEnabled(): boolean {
|
|
129
|
-
return getEnvBoolean("GEMINI_DEBUG", false) ||
|
|
130
|
-
getEnvBoolean("DEBUG", false);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Validate that required environment variables are set
|
|
135
|
-
* @returns Array of missing variable names
|
|
136
|
-
*/
|
|
137
|
-
export function validateEnv(requiredVars: string[]): string[] {
|
|
138
|
-
const missing: string[] = [];
|
|
139
|
-
|
|
140
|
-
for (const varName of requiredVars) {
|
|
141
|
-
if (!getEnvVar(varName)) {
|
|
142
|
-
missing.push(varName);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return missing;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Get configuration from environment or use provided fallback
|
|
151
|
-
* @throws Error if API key is not configured in either env or fallback
|
|
152
|
-
*/
|
|
153
|
-
export function getGeminiConfigFromEnv(fallback?: {
|
|
154
|
-
apiKey?: string;
|
|
155
|
-
timeout?: number;
|
|
156
|
-
}): {
|
|
157
|
-
apiKey: string;
|
|
158
|
-
timeout?: number;
|
|
159
|
-
} {
|
|
160
|
-
const env = loadGeminiEnv();
|
|
161
|
-
|
|
162
|
-
const apiKey = env.GEMINI_API_KEY || fallback?.apiKey;
|
|
163
|
-
|
|
164
|
-
if (!apiKey || apiKey.trim().length === 0) {
|
|
165
|
-
throw new Error(
|
|
166
|
-
"GEMINI_API_KEY must be set either in environment variables or provided as fallback. " +
|
|
167
|
-
"Set the GEMINI_API_KEY environment variable or pass apiKey in the fallback config."
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
apiKey,
|
|
173
|
-
timeout: env.GEMINI_TIMEOUT || fallback?.timeout,
|
|
174
|
-
};
|
|
175
|
-
}
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
export interface PerformanceMetrics {
|
|
3
|
-
duration: number;
|
|
4
|
-
timestamp: number;
|
|
5
|
-
metadata?: Record<string, unknown>;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export class PerformanceTimer {
|
|
9
|
-
private startTime: number;
|
|
10
|
-
private endTime?: number;
|
|
11
|
-
private metadata?: Record<string, unknown>;
|
|
12
|
-
|
|
13
|
-
constructor(metadata?: Record<string, unknown>) {
|
|
14
|
-
// Use performance.now() for higher precision when available
|
|
15
|
-
this.startTime = typeof performance !== "undefined" && performance.now
|
|
16
|
-
? performance.now()
|
|
17
|
-
: Date.now();
|
|
18
|
-
this.metadata = metadata;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
stop(): number {
|
|
22
|
-
this.endTime = typeof performance !== "undefined" && performance.now
|
|
23
|
-
? performance.now()
|
|
24
|
-
: Date.now();
|
|
25
|
-
return this.duration;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
get duration(): number {
|
|
29
|
-
const end = this.endTime ?? (typeof performance !== "undefined" && performance.now
|
|
30
|
-
? performance.now()
|
|
31
|
-
: Date.now());
|
|
32
|
-
return end - this.startTime;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
getMetrics(): PerformanceMetrics {
|
|
36
|
-
return { duration: this.duration, timestamp: this.startTime, metadata: this.metadata };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
get isRunning(): boolean {
|
|
40
|
-
return this.endTime === undefined;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export async function measureAsync<T>(
|
|
45
|
-
operation: () => Promise<T>,
|
|
46
|
-
metadata?: Record<string, unknown>,
|
|
47
|
-
): Promise<{ result: T; duration: number }> {
|
|
48
|
-
const timer = new PerformanceTimer(metadata);
|
|
49
|
-
try {
|
|
50
|
-
const result = await operation();
|
|
51
|
-
timer.stop();
|
|
52
|
-
return { result, duration: timer.duration };
|
|
53
|
-
} catch (error) {
|
|
54
|
-
timer.stop();
|
|
55
|
-
throw error;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function measureSync<T>(
|
|
60
|
-
operation: () => T,
|
|
61
|
-
metadata?: Record<string, unknown>,
|
|
62
|
-
): { result: T; duration: number } {
|
|
63
|
-
const timer = new PerformanceTimer(metadata);
|
|
64
|
-
try {
|
|
65
|
-
const result = operation();
|
|
66
|
-
timer.stop();
|
|
67
|
-
return { result, duration: timer.duration };
|
|
68
|
-
} catch (error) {
|
|
69
|
-
timer.stop();
|
|
70
|
-
throw error;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
interface DebouncedFunction<T extends (...args: never[]) => unknown> {
|
|
75
|
-
(...args: Parameters<T>): void;
|
|
76
|
-
cancel: () => void;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function debounce<T extends (...args: never[]) => unknown>(
|
|
80
|
-
func: T,
|
|
81
|
-
wait: number,
|
|
82
|
-
): DebouncedFunction<T> {
|
|
83
|
-
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
84
|
-
|
|
85
|
-
const debounced = (...args: Parameters<T>) => {
|
|
86
|
-
const later = () => {
|
|
87
|
-
timeout = undefined;
|
|
88
|
-
func(...args);
|
|
89
|
-
};
|
|
90
|
-
if (timeout) {
|
|
91
|
-
clearTimeout(timeout);
|
|
92
|
-
}
|
|
93
|
-
timeout = setTimeout(later, wait);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
debounced.cancel = () => {
|
|
97
|
-
if (timeout) {
|
|
98
|
-
clearTimeout(timeout);
|
|
99
|
-
timeout = undefined;
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
return debounced;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
interface ThrottledFunction<T extends (...args: never[]) => unknown> {
|
|
107
|
-
(...args: Parameters<T>): void;
|
|
108
|
-
cancel: () => void;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function throttle<T extends (...args: never[]) => unknown>(
|
|
112
|
-
func: T,
|
|
113
|
-
limit: number,
|
|
114
|
-
): ThrottledFunction<T> {
|
|
115
|
-
let inThrottle = false;
|
|
116
|
-
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
117
|
-
|
|
118
|
-
const throttled = (...args: Parameters<T>) => {
|
|
119
|
-
if (!inThrottle) {
|
|
120
|
-
func(...args);
|
|
121
|
-
inThrottle = true;
|
|
122
|
-
timeout = setTimeout(() => {
|
|
123
|
-
inThrottle = false;
|
|
124
|
-
timeout = undefined;
|
|
125
|
-
}, limit);
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
throttled.cancel = () => {
|
|
130
|
-
if (timeout) {
|
|
131
|
-
clearTimeout(timeout);
|
|
132
|
-
timeout = undefined;
|
|
133
|
-
inThrottle = false;
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
return throttled;
|
|
138
|
-
}
|
|
139
|
-
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
export interface RateLimiterOptions {
|
|
3
|
-
minInterval?: number; // Minimum milliseconds between requests
|
|
4
|
-
maxQueueSize?: number; // Maximum number of pending requests
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export class RateLimiter {
|
|
8
|
-
private queue: Array<() => Promise<void>> = [];
|
|
9
|
-
private processing = false;
|
|
10
|
-
private lastRequest = 0;
|
|
11
|
-
private minInterval: number;
|
|
12
|
-
private maxQueueSize: number;
|
|
13
|
-
|
|
14
|
-
constructor(options: RateLimiterOptions = {}) {
|
|
15
|
-
this.minInterval = options.minInterval ?? 100; // 100ms minimum interval
|
|
16
|
-
this.maxQueueSize = options.maxQueueSize ?? 100;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async execute<T>(fn: () => Promise<T>): Promise<T> {
|
|
20
|
-
if (this.queue.length >= this.maxQueueSize) {
|
|
21
|
-
// Calculate estimated wait time to help users understand delay
|
|
22
|
-
const estimatedWait = this.queue.length * this.minInterval;
|
|
23
|
-
const waitSeconds = (estimatedWait / 1000).toFixed(1);
|
|
24
|
-
throw new Error(
|
|
25
|
-
`Rate limiter queue is full (${this.maxQueueSize} requests pending). ` +
|
|
26
|
-
`Estimated wait: ~${waitSeconds}s. Please wait before retrying.`
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return new Promise((resolve, reject) => {
|
|
31
|
-
this.queue.push(async () => {
|
|
32
|
-
try {
|
|
33
|
-
const result = await fn();
|
|
34
|
-
resolve(result);
|
|
35
|
-
} catch (error) {
|
|
36
|
-
reject(error);
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// Start queue processing if not already running
|
|
41
|
-
this.processQueue().catch(() => {
|
|
42
|
-
// Individual task errors are handled above, ignore queue processing errors
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
private async processQueue(): Promise<void> {
|
|
48
|
-
// Only one processQueue can run at a time
|
|
49
|
-
if (this.processing) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
this.processing = true;
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
while (this.queue.length > 0) {
|
|
57
|
-
const elapsed = Date.now() - this.lastRequest;
|
|
58
|
-
if (elapsed < this.minInterval) {
|
|
59
|
-
await new Promise((r) => setTimeout(r, this.minInterval - elapsed));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const task = this.queue.shift();
|
|
63
|
-
if (task) {
|
|
64
|
-
this.lastRequest = Date.now();
|
|
65
|
-
await task();
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
} finally {
|
|
69
|
-
this.processing = false;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
getQueueSize(): number {
|
|
74
|
-
return this.queue.length;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
clear(): void {
|
|
78
|
-
this.queue = [];
|
|
79
|
-
this.processing = false;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
reset(): void {
|
|
83
|
-
this.clear();
|
|
84
|
-
this.lastRequest = 0;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Retry Utilities
|
|
3
|
-
* Implements retry logic with exponential backoff for resilient API calls
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { measureAsync } from "./performance.util";
|
|
7
|
-
|
|
8
|
-
export interface RetryOptions {
|
|
9
|
-
/** Maximum number of retry attempts */
|
|
10
|
-
maxAttempts?: number;
|
|
11
|
-
/** Initial delay in milliseconds */
|
|
12
|
-
initialDelay?: number;
|
|
13
|
-
/** Maximum delay in milliseconds */
|
|
14
|
-
maxDelay?: number;
|
|
15
|
-
/** Exponential backoff multiplier */
|
|
16
|
-
backoffMultiplier?: number;
|
|
17
|
-
/** Jitter factor to add randomness (0-1) */
|
|
18
|
-
jitterFactor?: number;
|
|
19
|
-
/** Whether to retry on specific error types */
|
|
20
|
-
shouldRetry?: (error: unknown) => boolean;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface RetryResult<T> {
|
|
24
|
-
result: T;
|
|
25
|
-
attempts: number;
|
|
26
|
-
totalDuration: number;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const DEFAULT_RETRY_OPTIONS: Required<RetryOptions> = {
|
|
30
|
-
maxAttempts: 3,
|
|
31
|
-
initialDelay: 1000,
|
|
32
|
-
maxDelay: 10000,
|
|
33
|
-
backoffMultiplier: 2,
|
|
34
|
-
jitterFactor: 0.1,
|
|
35
|
-
shouldRetry: () => true,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Calculate delay with exponential backoff and jitter
|
|
40
|
-
*/
|
|
41
|
-
function calculateDelay(
|
|
42
|
-
attempt: number,
|
|
43
|
-
options: Required<RetryOptions>
|
|
44
|
-
): number {
|
|
45
|
-
const exponentialDelay = options.initialDelay * Math.pow(options.backoffMultiplier, attempt);
|
|
46
|
-
|
|
47
|
-
// Apply jitter to prevent thundering herd
|
|
48
|
-
const jitter = exponentialDelay * options.jitterFactor * (Math.random() * 2 - 1);
|
|
49
|
-
|
|
50
|
-
return Math.min(
|
|
51
|
-
Math.max(exponentialDelay + jitter, options.initialDelay),
|
|
52
|
-
options.maxDelay
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Sleep for a specified duration
|
|
58
|
-
*/
|
|
59
|
-
function sleep(ms: number): Promise<void> {
|
|
60
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Execute operation with retry logic and exponential backoff
|
|
65
|
-
*/
|
|
66
|
-
export async function retryWithBackoff<T>(
|
|
67
|
-
operation: () => Promise<T>,
|
|
68
|
-
options: RetryOptions = {}
|
|
69
|
-
): Promise<RetryResult<T>> {
|
|
70
|
-
const opts = { ...DEFAULT_RETRY_OPTIONS, ...options };
|
|
71
|
-
|
|
72
|
-
let lastError: unknown;
|
|
73
|
-
|
|
74
|
-
for (let attempt = 0; attempt < opts.maxAttempts; attempt++) {
|
|
75
|
-
try {
|
|
76
|
-
const { result, duration } = await measureAsync(operation);
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
result,
|
|
80
|
-
attempts: attempt + 1,
|
|
81
|
-
totalDuration: duration,
|
|
82
|
-
};
|
|
83
|
-
} catch (error) {
|
|
84
|
-
lastError = error;
|
|
85
|
-
|
|
86
|
-
// Check if we should retry this error
|
|
87
|
-
if (!opts.shouldRetry(error)) {
|
|
88
|
-
throw error;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Don't delay after the last attempt
|
|
92
|
-
if (attempt < opts.maxAttempts - 1) {
|
|
93
|
-
const delay = calculateDelay(attempt, opts);
|
|
94
|
-
await sleep(delay);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
throw lastError;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Create a retry predicate based on error type/message
|
|
104
|
-
*/
|
|
105
|
-
export function createRetryPredicate(
|
|
106
|
-
retryablePatterns: string[]
|
|
107
|
-
): (error: unknown) => boolean {
|
|
108
|
-
return (error: unknown) => {
|
|
109
|
-
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
110
|
-
return retryablePatterns.some((pattern) => message.includes(pattern.toLowerCase()));
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Default retry predicate for common retryable errors
|
|
116
|
-
*/
|
|
117
|
-
export const shouldRetryNetworkError = createRetryPredicate([
|
|
118
|
-
"network",
|
|
119
|
-
"timeout",
|
|
120
|
-
"rate limit",
|
|
121
|
-
"too many requests",
|
|
122
|
-
"500",
|
|
123
|
-
"502",
|
|
124
|
-
"503",
|
|
125
|
-
"504",
|
|
126
|
-
"econnreset",
|
|
127
|
-
"etimedout",
|
|
128
|
-
]);
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Retry only on specific conditions
|
|
132
|
-
*/
|
|
133
|
-
export async function retryIf<T>(
|
|
134
|
-
operation: () => Promise<T>,
|
|
135
|
-
shouldRetryFn: (error: unknown) => boolean,
|
|
136
|
-
options?: Omit<RetryOptions, "shouldRetry">
|
|
137
|
-
): Promise<RetryResult<T>> {
|
|
138
|
-
return retryWithBackoff(operation, {
|
|
139
|
-
...options,
|
|
140
|
-
shouldRetry: shouldRetryFn,
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Execute with fixed delay between attempts (no exponential backoff)
|
|
146
|
-
*/
|
|
147
|
-
export async function retryWithFixedDelay<T>(
|
|
148
|
-
operation: () => Promise<T>,
|
|
149
|
-
delay: number = 1000,
|
|
150
|
-
maxAttempts: number = 3
|
|
151
|
-
): Promise<RetryResult<T>> {
|
|
152
|
-
return retryWithBackoff(operation, {
|
|
153
|
-
maxAttempts,
|
|
154
|
-
initialDelay: delay,
|
|
155
|
-
backoffMultiplier: 1,
|
|
156
|
-
jitterFactor: 0,
|
|
157
|
-
});
|
|
158
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { DEFAULT_MODELS } from "../domain/entities";
|
|
3
|
-
|
|
4
|
-
export interface ProviderPreferences {
|
|
5
|
-
/** Request timeout (ms) */
|
|
6
|
-
timeout?: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface ProviderConfigInput {
|
|
10
|
-
/** API key for authentication */
|
|
11
|
-
apiKey: string;
|
|
12
|
-
/** Optional user preferences */
|
|
13
|
-
preferences?: ProviderPreferences;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface ResolvedProviderConfig {
|
|
17
|
-
apiKey: string;
|
|
18
|
-
textModel: string;
|
|
19
|
-
timeout: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const DEFAULTS = {
|
|
23
|
-
timeout: 30000,
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export function resolveProviderConfig(
|
|
27
|
-
input: ProviderConfigInput,
|
|
28
|
-
): ResolvedProviderConfig {
|
|
29
|
-
const preferences = input.preferences || {};
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
apiKey: input.apiKey,
|
|
33
|
-
textModel: DEFAULT_MODELS.TEXT,
|
|
34
|
-
timeout: preferences.timeout ?? DEFAULTS.timeout,
|
|
35
|
-
};
|
|
36
|
-
}
|