@umituz/react-native-ai-gemini-provider 1.0.6 → 1.1.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/package.json
CHANGED
|
@@ -25,6 +25,37 @@ const DEFAULT_CONFIG: Partial<GeminiConfig> = {
|
|
|
25
25
|
imageModel: "gemini-2.0-flash-exp",
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
const RETRYABLE_ERROR_PATTERNS = [
|
|
29
|
+
"rate limit",
|
|
30
|
+
"too many requests",
|
|
31
|
+
"429",
|
|
32
|
+
"500",
|
|
33
|
+
"502",
|
|
34
|
+
"503",
|
|
35
|
+
"504",
|
|
36
|
+
"timeout",
|
|
37
|
+
"network",
|
|
38
|
+
"econnrefused",
|
|
39
|
+
"fetch failed",
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
function isRetryableError(error: unknown): boolean {
|
|
43
|
+
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
44
|
+
return RETRYABLE_ERROR_PATTERNS.some((pattern) => message.includes(pattern));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function sleep(ms: number): Promise<void> {
|
|
48
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function extractBase64Data(base64String: string): string {
|
|
52
|
+
if (!base64String.includes(",")) {
|
|
53
|
+
return base64String;
|
|
54
|
+
}
|
|
55
|
+
const parts = base64String.split(",");
|
|
56
|
+
return parts[1] ?? parts[0] ?? base64String;
|
|
57
|
+
}
|
|
58
|
+
|
|
28
59
|
class GeminiClientService {
|
|
29
60
|
private client: GoogleGenerativeAI | null = null;
|
|
30
61
|
private config: GeminiConfig | null = null;
|
|
@@ -98,10 +129,12 @@ class GeminiClientService {
|
|
|
98
129
|
}),
|
|
99
130
|
}));
|
|
100
131
|
|
|
101
|
-
const result = await
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
132
|
+
const result = await this.executeWithRetry(() =>
|
|
133
|
+
genModel.generateContent({
|
|
134
|
+
contents: sdkContents as Parameters<typeof genModel.generateContent>[0] extends { contents: infer C } ? C : never,
|
|
135
|
+
generationConfig,
|
|
136
|
+
}),
|
|
137
|
+
);
|
|
105
138
|
|
|
106
139
|
const response = result.response;
|
|
107
140
|
|
|
@@ -159,15 +192,10 @@ class GeminiClientService {
|
|
|
159
192
|
const parts: GeminiContent["parts"] = [{ text: prompt }];
|
|
160
193
|
|
|
161
194
|
for (const image of images) {
|
|
162
|
-
// Remove data URL prefix if present
|
|
163
|
-
const base64Data = image.base64.includes(",")
|
|
164
|
-
? image.base64.split(",")[1]
|
|
165
|
-
: image.base64;
|
|
166
|
-
|
|
167
195
|
parts.push({
|
|
168
196
|
inlineData: {
|
|
169
197
|
mimeType: image.mimeType,
|
|
170
|
-
data:
|
|
198
|
+
data: extractBase64Data(image.base64),
|
|
171
199
|
},
|
|
172
200
|
});
|
|
173
201
|
}
|
|
@@ -287,6 +315,33 @@ class GeminiClientService {
|
|
|
287
315
|
return fullText;
|
|
288
316
|
}
|
|
289
317
|
|
|
318
|
+
private async executeWithRetry<T>(
|
|
319
|
+
operation: () => Promise<T>,
|
|
320
|
+
retryCount = 0,
|
|
321
|
+
): Promise<T> {
|
|
322
|
+
const maxRetries = this.config?.maxRetries ?? 3;
|
|
323
|
+
const baseDelay = this.config?.baseDelay ?? 1000;
|
|
324
|
+
const maxDelay = this.config?.maxDelay ?? 10000;
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
return await operation();
|
|
328
|
+
} catch (error) {
|
|
329
|
+
if (!isRetryableError(error) || retryCount >= maxRetries) {
|
|
330
|
+
throw error;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const delay = Math.min(baseDelay * Math.pow(2, retryCount), maxDelay);
|
|
334
|
+
|
|
335
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
336
|
+
// eslint-disable-next-line no-console
|
|
337
|
+
console.log(`[Gemini] Retry ${retryCount + 1}/${maxRetries} after ${delay}ms`);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
await sleep(delay);
|
|
341
|
+
return this.executeWithRetry(operation, retryCount + 1);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
290
345
|
private extractTextFromResponse(response: GeminiResponse): string {
|
|
291
346
|
const candidate = response.candidates?.[0];
|
|
292
347
|
|
|
@@ -13,6 +13,14 @@ import { geminiClientService } from "./gemini-client.service";
|
|
|
13
13
|
|
|
14
14
|
declare const __DEV__: boolean;
|
|
15
15
|
|
|
16
|
+
function extractBase64Data(base64String: string): string {
|
|
17
|
+
if (!base64String.includes(",")) {
|
|
18
|
+
return base64String;
|
|
19
|
+
}
|
|
20
|
+
const parts = base64String.split(",");
|
|
21
|
+
return parts[1] ?? parts[0] ?? base64String;
|
|
22
|
+
}
|
|
23
|
+
|
|
16
24
|
export interface AIProviderConfig {
|
|
17
25
|
apiKey: string;
|
|
18
26
|
maxRetries?: number;
|
|
@@ -262,13 +270,10 @@ class GeminiProviderService {
|
|
|
262
270
|
// Handle multiple images
|
|
263
271
|
if (Array.isArray(input.images)) {
|
|
264
272
|
for (const img of input.images as GeminiImageInput[]) {
|
|
265
|
-
const base64Data = img.base64.includes(",")
|
|
266
|
-
? img.base64.split(",")[1]
|
|
267
|
-
: img.base64;
|
|
268
273
|
parts.push({
|
|
269
274
|
inlineData: {
|
|
270
275
|
mimeType: img.mimeType,
|
|
271
|
-
data:
|
|
276
|
+
data: extractBase64Data(img.base64),
|
|
272
277
|
},
|
|
273
278
|
});
|
|
274
279
|
}
|