@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-gemini-provider",
3
- "version": "1.0.6",
3
+ "version": "1.1.0",
4
4
  "description": "Google Gemini AI provider for React Native applications",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -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 genModel.generateContent({
102
- contents: sdkContents as Parameters<typeof genModel.generateContent>[0] extends { contents: infer C } ? C : never,
103
- generationConfig,
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: base64Data,
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: base64Data,
276
+ data: extractBase64Data(img.base64),
272
277
  },
273
278
  });
274
279
  }