@umituz/react-native-ai-gemini-provider 1.8.3 → 1.9.1

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.8.3",
3
+ "version": "1.9.1",
4
4
  "description": "Google Gemini AI provider for React Native applications",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -5,13 +5,14 @@
5
5
 
6
6
  /**
7
7
  * Available Gemini models
8
+ * Updated: 2025-12 with latest stable and preview models
8
9
  */
9
10
  export const GEMINI_MODELS = {
10
11
  // Text generation models
11
12
  TEXT: {
12
- FLASH: "gemini-2.0-flash",
13
- FLASH_LITE: "gemini-2.0-flash-lite",
14
- PRO: "gemini-1.5-pro",
13
+ FLASH: "gemini-2.5-flash",
14
+ FLASH_LITE: "gemini-2.5-flash-lite",
15
+ PRO: "gemini-2.5-pro",
15
16
  },
16
17
 
17
18
  // Text-to-Image models (Imagen 4.0) - generates images from text only
@@ -19,14 +20,17 @@ export const GEMINI_MODELS = {
19
20
  DEFAULT: "imagen-4.0-generate-001",
20
21
  },
21
22
 
22
- // Image editing models (Gemini) - transforms/edits images with input image + prompt
23
+ // Image editing models - transforms/edits images with input image + prompt
24
+ // gemini-3-pro-image-preview is the highest quality for identity preservation
23
25
  IMAGE_EDIT: {
24
- DEFAULT: "gemini-2.0-flash-exp-image-generation",
26
+ DEFAULT: "gemini-3-pro-image-preview",
27
+ FAST: "gemini-2.5-flash-image",
28
+ LEGACY: "gemini-2.0-flash-preview-image-generation",
25
29
  },
26
30
 
27
31
  // Video understanding models
28
32
  VIDEO: {
29
- FLASH: "gemini-2.0-flash",
33
+ FLASH: "gemini-2.5-flash",
30
34
  },
31
35
  } as const;
32
36
 
@@ -50,3 +54,5 @@ export const RESPONSE_MODALITIES = {
50
54
  } as const;
51
55
 
52
56
  export type ResponseModality = "TEXT" | "IMAGE";
57
+
58
+
package/src/index.ts CHANGED
@@ -56,7 +56,12 @@ export type { ResponseModality } from "./domain/entities";
56
56
  // =============================================================================
57
57
 
58
58
  export {
59
- geminiClientService,
59
+ geminiClientCoreService,
60
+ geminiRetryService,
61
+ geminiTextGenerationService,
62
+ geminiImageGenerationService,
63
+ geminiImageEditService,
64
+ geminiStreamingService,
60
65
  geminiProviderService,
61
66
  createGeminiProvider,
62
67
  } from "./infrastructure/services";
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Gemini Client Core Service
3
+ * Handles client initialization, configuration, and validation
4
+ */
5
+
6
+ import { GoogleGenerativeAI, type GenerativeModel } from "@google/generative-ai";
7
+ import { DEFAULT_MODELS } from "../../domain/entities";
8
+ import type { GeminiConfig } from "../../domain/entities";
9
+
10
+ declare const __DEV__: boolean;
11
+
12
+ const DEFAULT_CONFIG: Partial<GeminiConfig> = {
13
+ maxRetries: 3,
14
+ baseDelay: 1000,
15
+ maxDelay: 10000,
16
+ defaultTimeoutMs: 60000,
17
+ defaultModel: DEFAULT_MODELS.TEXT,
18
+ imageModel: DEFAULT_MODELS.TEXT_TO_IMAGE,
19
+ };
20
+
21
+ class GeminiClientCoreService {
22
+ private client: GoogleGenerativeAI | null = null;
23
+ private config: GeminiConfig | null = null;
24
+ private initialized = false;
25
+
26
+ initialize(config: GeminiConfig): void {
27
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
28
+ // eslint-disable-next-line no-console
29
+ console.log("[GeminiClient] initialize() called", {
30
+ hasApiKey: !!config.apiKey,
31
+ defaultModel: config.defaultModel,
32
+ imageModel: config.imageModel,
33
+ });
34
+ }
35
+
36
+ this.client = new GoogleGenerativeAI(config.apiKey);
37
+ this.config = { ...DEFAULT_CONFIG, ...config };
38
+ this.initialized = true;
39
+
40
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
41
+ // eslint-disable-next-line no-console
42
+ console.log("[GeminiClient] initialized successfully", {
43
+ defaultModel: this.config.defaultModel,
44
+ imageModel: this.config.imageModel,
45
+ maxRetries: this.config.maxRetries,
46
+ });
47
+ }
48
+ }
49
+
50
+ isInitialized(): boolean {
51
+ return this.initialized;
52
+ }
53
+
54
+ getConfig(): GeminiConfig | null {
55
+ return this.config;
56
+ }
57
+
58
+ getClient(): GoogleGenerativeAI | null {
59
+ return this.client;
60
+ }
61
+
62
+ validateInitialization(): void {
63
+ if (!this.client || !this.initialized) {
64
+ throw new Error(
65
+ "Gemini client not initialized. Call initialize() first.",
66
+ );
67
+ }
68
+ }
69
+
70
+ getModel(modelName?: string): GenerativeModel {
71
+ this.validateInitialization();
72
+ const effectiveModel = modelName || this.config?.defaultModel || "gemini-1.5-flash";
73
+ return this.client!.getGenerativeModel({ model: effectiveModel });
74
+ }
75
+
76
+ reset(): void {
77
+ this.client = null;
78
+ this.config = null;
79
+ this.initialized = false;
80
+ }
81
+ }
82
+
83
+ export const geminiClientCoreService = new GeminiClientCoreService();
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Gemini Image Edit Service
3
+ * Handles image editing/transformation using Gemini API
4
+ */
5
+
6
+ import { geminiClientCoreService } from "./gemini-client-core.service";
7
+ import { geminiRetryService } from "./gemini-retry.service";
8
+ import { extractBase64Data } from "../utils/gemini-data-transformer.util";
9
+ import { DEFAULT_MODELS } from "../../domain/entities";
10
+ import type { GeminiImageGenerationResult } from "../../domain/entities";
11
+
12
+ declare const __DEV__: boolean;
13
+
14
+ interface GeminiContentResponse {
15
+ candidates?: Array<{
16
+ content?: {
17
+ parts?: Array<{
18
+ text?: string;
19
+ inlineData?: {
20
+ data?: string;
21
+ mimeType?: string;
22
+ };
23
+ }>;
24
+ };
25
+ }>;
26
+ }
27
+
28
+ class GeminiImageEditService {
29
+ /**
30
+ * Edit/transform image using Gemini generateContent API
31
+ * Takes input image + prompt and generates new image
32
+ */
33
+ async editImage(
34
+ prompt: string,
35
+ images: Array<{ base64: string; mimeType: string }>,
36
+ ): Promise<GeminiImageGenerationResult> {
37
+ geminiClientCoreService.validateInitialization();
38
+
39
+ const config = geminiClientCoreService.getConfig();
40
+ const editModel = DEFAULT_MODELS.IMAGE_EDIT;
41
+ const apiKey = config?.apiKey;
42
+
43
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
44
+ // eslint-disable-next-line no-console
45
+ console.log("[GeminiClient] editImage() called", {
46
+ model: editModel,
47
+ promptLength: prompt.length,
48
+ imagesCount: images.length,
49
+ });
50
+ }
51
+
52
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${editModel}:generateContent`;
53
+
54
+ const parts: Array<Record<string, unknown>> = [];
55
+
56
+ for (const image of images) {
57
+ parts.push({
58
+ inlineData: {
59
+ mimeType: image.mimeType,
60
+ data: extractBase64Data(image.base64),
61
+ },
62
+ });
63
+ }
64
+
65
+ parts.push({ text: prompt });
66
+
67
+ const requestBody = {
68
+ contents: [{ parts }],
69
+ generationConfig: {
70
+ responseModalities: ["TEXT", "IMAGE"],
71
+ },
72
+ };
73
+
74
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
75
+ // eslint-disable-next-line no-console
76
+ console.log("[GeminiClient] editImage() request", {
77
+ url,
78
+ partsCount: parts.length,
79
+ });
80
+ }
81
+
82
+ const response = await geminiRetryService.executeWithRetry(async () => {
83
+ const res = await fetch(url, {
84
+ method: "POST",
85
+ headers: {
86
+ "Content-Type": "application/json",
87
+ "x-goog-api-key": apiKey!,
88
+ },
89
+ body: JSON.stringify(requestBody),
90
+ });
91
+
92
+ if (!res.ok) {
93
+ const errorText = await res.text();
94
+ throw new Error(`Image edit API error (${res.status}): ${errorText}`);
95
+ }
96
+
97
+ return res.json() as Promise<GeminiContentResponse>;
98
+ });
99
+
100
+ const result: GeminiImageGenerationResult = {
101
+ text: undefined,
102
+ imageUrl: undefined,
103
+ imageBase64: undefined,
104
+ mimeType: "image/png",
105
+ };
106
+
107
+ const candidate = response.candidates?.[0];
108
+ const responseParts = candidate?.content?.parts || [];
109
+
110
+ for (const part of responseParts) {
111
+ if (part.text) {
112
+ result.text = part.text;
113
+ }
114
+ if (part.inlineData) {
115
+ result.imageBase64 = part.inlineData.data;
116
+ result.mimeType = part.inlineData.mimeType || "image/png";
117
+ result.imageUrl = `data:${result.mimeType};base64,${result.imageBase64}`;
118
+ }
119
+ }
120
+
121
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
122
+ // eslint-disable-next-line no-console
123
+ console.log("[GeminiClient] editImage() completed", {
124
+ hasImage: !!result.imageBase64,
125
+ hasText: !!result.text,
126
+ imageDataLength: result.imageBase64?.length ?? 0,
127
+ });
128
+ }
129
+
130
+ return result;
131
+ }
132
+ }
133
+
134
+ export const geminiImageEditService = new GeminiImageEditService();
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Gemini Image Generation Service
3
+ * Handles image generation using Imagen API
4
+ */
5
+
6
+ import { geminiClientCoreService } from "./gemini-client-core.service";
7
+ import { geminiRetryService } from "./gemini-retry.service";
8
+ import { DEFAULT_MODELS } from "../../domain/entities";
9
+ import type {
10
+ GeminiGenerationConfig,
11
+ GeminiImageGenerationResult,
12
+ } from "../../domain/entities";
13
+
14
+ declare const __DEV__: boolean;
15
+
16
+ interface ImagenApiResponse {
17
+ generatedImages?: Array<{
18
+ image?: {
19
+ imageBytes?: string;
20
+ };
21
+ }>;
22
+ }
23
+
24
+ class GeminiImageGenerationService {
25
+ /**
26
+ * Generate image from prompt using Imagen API
27
+ * Uses REST API endpoint: /v1beta/models/{model}:predict
28
+ */
29
+ async generateImage(
30
+ prompt: string,
31
+ _images?: Array<{ base64: string; mimeType: string }>,
32
+ _config?: GeminiGenerationConfig,
33
+ ): Promise<GeminiImageGenerationResult> {
34
+ geminiClientCoreService.validateInitialization();
35
+
36
+ const config = geminiClientCoreService.getConfig();
37
+ const imageModel = config?.imageModel || DEFAULT_MODELS.TEXT_TO_IMAGE;
38
+ const apiKey = config?.apiKey;
39
+
40
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
41
+ // eslint-disable-next-line no-console
42
+ console.log("[GeminiClient] generateImage() called (Imagen API)", {
43
+ model: imageModel,
44
+ promptLength: prompt.length,
45
+ });
46
+ }
47
+
48
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${imageModel}:predict`;
49
+
50
+ const requestBody = {
51
+ instances: [{ prompt }],
52
+ parameters: {
53
+ sampleCount: 1,
54
+ aspectRatio: "1:1",
55
+ },
56
+ };
57
+
58
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
59
+ // eslint-disable-next-line no-console
60
+ console.log("[GeminiClient] Imagen API request", {
61
+ url,
62
+ prompt: prompt.substring(0, 100) + "...",
63
+ });
64
+ }
65
+
66
+ const response = await geminiRetryService.executeWithRetry(async () => {
67
+ const res = await fetch(url, {
68
+ method: "POST",
69
+ headers: {
70
+ "Content-Type": "application/json",
71
+ "x-goog-api-key": apiKey!,
72
+ },
73
+ body: JSON.stringify(requestBody),
74
+ });
75
+
76
+ if (!res.ok) {
77
+ const errorText = await res.text();
78
+ throw new Error(`Imagen API error (${res.status}): ${errorText}`);
79
+ }
80
+
81
+ return res.json() as Promise<ImagenApiResponse>;
82
+ });
83
+
84
+ const result: GeminiImageGenerationResult = {
85
+ text: undefined,
86
+ imageUrl: undefined,
87
+ imageBase64: undefined,
88
+ mimeType: "image/png",
89
+ };
90
+
91
+ if (response.generatedImages && response.generatedImages.length > 0) {
92
+ const generatedImage = response.generatedImages[0];
93
+ const imageBytes = generatedImage.image?.imageBytes;
94
+
95
+ if (imageBytes) {
96
+ result.imageBase64 = imageBytes;
97
+ result.imageUrl = `data:image/png;base64,${imageBytes}`;
98
+ }
99
+ }
100
+
101
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
102
+ // eslint-disable-next-line no-console
103
+ console.log("[GeminiClient] generateImage() completed (Imagen)", {
104
+ hasImage: !!result.imageBase64,
105
+ imageDataLength: result.imageBase64?.length ?? 0,
106
+ });
107
+ }
108
+
109
+ return result;
110
+ }
111
+ }
112
+
113
+ export const geminiImageGenerationService = new GeminiImageGenerationService();
@@ -9,18 +9,14 @@ import type {
9
9
  GeminiImageInput,
10
10
  GeminiImageGenerationResult,
11
11
  } from "../../domain/entities";
12
- import { geminiClientService } from "./gemini-client.service";
12
+ import { geminiClientCoreService } from "./gemini-client-core.service";
13
+ import { geminiTextGenerationService } from "./gemini-text-generation.service";
14
+ import { geminiImageGenerationService } from "./gemini-image-generation.service";
15
+ import { geminiImageEditService } from "./gemini-image-edit.service";
16
+ import { extractBase64Data } from "../utils/gemini-data-transformer.util";
13
17
 
14
18
  declare const __DEV__: boolean;
15
19
 
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
-
24
20
  export interface AIProviderConfig {
25
21
  apiKey: string;
26
22
  maxRetries?: number;
@@ -84,7 +80,7 @@ class GeminiProviderService {
84
80
  imageModel: config.imageModel,
85
81
  };
86
82
 
87
- geminiClientService.initialize(geminiConfig);
83
+ geminiClientCoreService.initialize(geminiConfig);
88
84
 
89
85
  if (typeof __DEV__ !== "undefined" && __DEV__) {
90
86
  // eslint-disable-next-line no-console
@@ -93,7 +89,7 @@ class GeminiProviderService {
93
89
  }
94
90
 
95
91
  isInitialized(): boolean {
96
- return geminiClientService.isInitialized();
92
+ return geminiClientCoreService.isInitialized();
97
93
  }
98
94
 
99
95
  submitJob(
@@ -207,7 +203,7 @@ class GeminiProviderService {
207
203
  async generateImage(
208
204
  prompt: string,
209
205
  ): Promise<GeminiImageGenerationResult> {
210
- return geminiClientService.generateImage(prompt);
206
+ return geminiImageGenerationService.generateImage(prompt);
211
207
  }
212
208
 
213
209
  /**
@@ -218,7 +214,7 @@ class GeminiProviderService {
218
214
  prompt: string,
219
215
  images: GeminiImageInput[],
220
216
  ): Promise<GeminiImageGenerationResult> {
221
- return geminiClientService.editImage(prompt, images);
217
+ return geminiImageEditService.editImage(prompt, images);
222
218
  }
223
219
 
224
220
  /**
@@ -238,7 +234,7 @@ class GeminiProviderService {
238
234
  });
239
235
  }
240
236
 
241
- const response = await geminiClientService.generateWithImages(
237
+ const response = await geminiTextGenerationService.generateWithImages(
242
238
  model,
243
239
  prompt,
244
240
  images,
@@ -261,7 +257,7 @@ class GeminiProviderService {
261
257
  }
262
258
 
263
259
  reset(): void {
264
- geminiClientService.reset();
260
+ geminiClientCoreService.reset();
265
261
  this.pendingJobs.clear();
266
262
  this.jobCounter = 0;
267
263
  }
@@ -306,12 +302,12 @@ class GeminiProviderService {
306
302
  if (isImageGeneration) {
307
303
  const prompt = String(input.prompt || "");
308
304
  const images = input.images as GeminiImageInput[] | undefined;
309
- const result = await geminiClientService.generateImage(prompt, images);
305
+ const result = await geminiImageGenerationService.generateImage(prompt, images);
310
306
  return result as T;
311
307
  }
312
308
 
313
309
  const contents = this.buildContents(input);
314
- const response = await geminiClientService.generateContent(
310
+ const response = await geminiTextGenerationService.generateContent(
315
311
  model,
316
312
  contents,
317
313
  input.generationConfig as undefined,
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Gemini Retry Service
3
+ * Handles retry logic with exponential backoff
4
+ */
5
+
6
+ import { geminiClientCoreService } from "./gemini-client-core.service";
7
+
8
+ declare const __DEV__: boolean;
9
+
10
+ const RETRYABLE_ERROR_PATTERNS = [
11
+ "rate limit",
12
+ "too many requests",
13
+ "429",
14
+ "500",
15
+ "502",
16
+ "503",
17
+ "504",
18
+ "timeout",
19
+ "network",
20
+ "econnrefused",
21
+ "fetch failed",
22
+ ];
23
+
24
+ function isRetryableError(error: unknown): boolean {
25
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
26
+ return RETRYABLE_ERROR_PATTERNS.some((pattern) => message.includes(pattern));
27
+ }
28
+
29
+ function sleep(ms: number): Promise<void> {
30
+ return new Promise((resolve) => setTimeout(resolve, ms));
31
+ }
32
+
33
+ class GeminiRetryService {
34
+ async executeWithRetry<T>(
35
+ operation: () => Promise<T>,
36
+ retryCount = 0,
37
+ ): Promise<T> {
38
+ const config = geminiClientCoreService.getConfig();
39
+ const maxRetries = config?.maxRetries ?? 3;
40
+ const baseDelay = config?.baseDelay ?? 1000;
41
+ const maxDelay = config?.maxDelay ?? 10000;
42
+
43
+ try {
44
+ return await operation();
45
+ } catch (error) {
46
+ if (!isRetryableError(error) || retryCount >= maxRetries) {
47
+ throw error;
48
+ }
49
+
50
+ const delay = Math.min(baseDelay * Math.pow(2, retryCount), maxDelay);
51
+
52
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
53
+ // eslint-disable-next-line no-console
54
+ console.log(`[Gemini] Retry ${retryCount + 1}/${maxRetries} after ${delay}ms`);
55
+ }
56
+
57
+ await sleep(delay);
58
+ return this.executeWithRetry(operation, retryCount + 1);
59
+ }
60
+ }
61
+ }
62
+
63
+ export const geminiRetryService = new GeminiRetryService();
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Gemini Streaming Service
3
+ * Handles streaming content generation
4
+ */
5
+
6
+ import { geminiClientCoreService } from "./gemini-client-core.service";
7
+ import type {
8
+ GeminiContent,
9
+ GeminiGenerationConfig,
10
+ } from "../../domain/entities";
11
+
12
+ class GeminiStreamingService {
13
+ /**
14
+ * Stream content generation
15
+ */
16
+ async streamContent(
17
+ model: string,
18
+ contents: GeminiContent[],
19
+ onChunk: (text: string) => void,
20
+ generationConfig?: GeminiGenerationConfig,
21
+ ): Promise<string> {
22
+ const genModel = geminiClientCoreService.getModel(model);
23
+
24
+ const sdkContents = contents.map((content) => ({
25
+ role: content.role || "user",
26
+ parts: content.parts.map((part) => {
27
+ if ("text" in part) {
28
+ return { text: part.text };
29
+ }
30
+ if ("inlineData" in part) {
31
+ return {
32
+ inlineData: {
33
+ mimeType: part.inlineData.mimeType,
34
+ data: part.inlineData.data,
35
+ },
36
+ };
37
+ }
38
+ return part;
39
+ }),
40
+ }));
41
+
42
+ const result = await genModel.generateContentStream({
43
+ contents: sdkContents as Parameters<typeof genModel.generateContentStream>[0] extends { contents: infer C } ? C : never,
44
+ generationConfig,
45
+ });
46
+
47
+ let fullText = "";
48
+
49
+ for await (const chunk of result.stream) {
50
+ const chunkText = chunk.text();
51
+ if (chunkText) {
52
+ fullText += chunkText;
53
+ onChunk(chunkText);
54
+ }
55
+ }
56
+
57
+ return fullText;
58
+ }
59
+ }
60
+
61
+ export const geminiStreamingService = new GeminiStreamingService();