@umituz/react-native-ai-gemini-provider 1.14.25 → 1.14.26

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.14.25",
3
+ "version": "1.14.26",
4
4
  "description": "Google Gemini AI provider for React Native applications",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
package/src/index.ts CHANGED
@@ -106,6 +106,7 @@ export type {
106
106
  GenerationInput,
107
107
  GenerationResult,
108
108
  ExecutionOptions,
109
+ RetryOptions,
109
110
  } from "./infrastructure/services";
110
111
 
111
112
  // =============================================================================
@@ -117,6 +118,16 @@ export {
117
118
  isGeminiErrorRetryable,
118
119
  categorizeGeminiError,
119
120
  createGeminiError,
121
+ // Model validation
122
+ isValidModel,
123
+ validateModel,
124
+ getSafeModel,
125
+ isTextModel,
126
+ isImageModel,
127
+ isImageEditModel,
128
+ isVideoGenerationModel,
129
+ getModelCategory,
130
+ getAllValidModels,
120
131
  // Input builders
121
132
  buildSingleImageInput,
122
133
  buildDualImageInput,
@@ -155,6 +166,17 @@ export type {
155
166
  UseGeminiReturn,
156
167
  } from "./presentation/hooks";
157
168
 
169
+ // =============================================================================
170
+ // TELEMETRY - Monitoring and Observability
171
+ // =============================================================================
172
+
173
+ export { telemetryHooks } from "./infrastructure/telemetry";
174
+
175
+ export type {
176
+ TelemetryEvent,
177
+ TelemetryListener,
178
+ } from "./infrastructure/telemetry";
179
+
158
180
  // =============================================================================
159
181
  // PROVIDER CONFIGURATION - Tier-based Setup
160
182
  // =============================================================================
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Gemini Retry Service
3
- * Handles retry logic with exponential backoff
3
+ * Handles retry logic with exponential backoff and jitter
4
+ * Jitter helps prevent thundering herd problem in distributed systems
4
5
  */
5
6
 
6
7
  import { geminiClientCoreService } from "./gemini-client-core.service";
@@ -26,19 +27,46 @@ function isRetryableError(error: unknown): boolean {
26
27
  return RETRYABLE_ERROR_PATTERNS.some((pattern) => message.includes(pattern));
27
28
  }
28
29
 
30
+ /**
31
+ * Add random jitter to delay to prevent synchronized retries
32
+ * Uses full jitter strategy: random between 0 and base_delay * 2^attempt
33
+ */
34
+ function calculateDelayWithJitter(
35
+ baseDelay: number,
36
+ retryCount: number,
37
+ maxDelay: number,
38
+ ): number {
39
+ const exponentialDelay = baseDelay * Math.pow(2, retryCount);
40
+ const cappedDelay = Math.min(exponentialDelay, maxDelay);
41
+ const jitter = Math.random() * cappedDelay;
42
+ return Math.floor(jitter);
43
+ }
44
+
29
45
  function sleep(ms: number): Promise<void> {
30
46
  return new Promise((resolve) => setTimeout(resolve, ms));
31
47
  }
32
48
 
49
+ export interface RetryOptions {
50
+ maxRetries?: number;
51
+ baseDelay?: number;
52
+ maxDelay?: number;
53
+ enableJitter?: boolean;
54
+ }
55
+
33
56
  class GeminiRetryService {
57
+ /**
58
+ * Execute operation with retry logic
59
+ */
34
60
  async executeWithRetry<T>(
35
61
  operation: () => Promise<T>,
36
62
  retryCount = 0,
63
+ options?: RetryOptions,
37
64
  ): Promise<T> {
38
65
  const config = geminiClientCoreService.getConfig();
39
- const maxRetries = config?.maxRetries ?? 3;
40
- const baseDelay = config?.baseDelay ?? 1000;
41
- const maxDelay = config?.maxDelay ?? 10000;
66
+ const maxRetries = options?.maxRetries ?? config?.maxRetries ?? 3;
67
+ const baseDelay = options?.baseDelay ?? config?.baseDelay ?? 1000;
68
+ const maxDelay = options?.maxDelay ?? config?.maxDelay ?? 10000;
69
+ const enableJitter = options?.enableJitter ?? true;
42
70
 
43
71
  try {
44
72
  return await operation();
@@ -47,17 +75,28 @@ class GeminiRetryService {
47
75
  throw error;
48
76
  }
49
77
 
50
- const delay = Math.min(baseDelay * Math.pow(2, retryCount), maxDelay);
78
+ const delay = enableJitter
79
+ ? calculateDelayWithJitter(baseDelay, retryCount, maxDelay)
80
+ : Math.min(baseDelay * Math.pow(2, retryCount), maxDelay);
51
81
 
52
82
  if (typeof __DEV__ !== "undefined" && __DEV__) {
53
83
  // eslint-disable-next-line no-console
54
- console.log(`[Gemini] Retry ${retryCount + 1}/${maxRetries} after ${delay}ms`);
84
+ console.log(`[Gemini] Retry ${retryCount + 1}/${maxRetries} after ${delay}ms`, {
85
+ jitter: enableJitter,
86
+ });
55
87
  }
56
88
 
57
89
  await sleep(delay);
58
- return this.executeWithRetry(operation, retryCount + 1);
90
+ return this.executeWithRetry(operation, retryCount + 1, options);
59
91
  }
60
92
  }
93
+
94
+ /**
95
+ * Check if an error is retryable
96
+ */
97
+ isRetryableError(error: unknown): boolean {
98
+ return isRetryableError(error);
99
+ }
61
100
  }
62
101
 
63
102
  export const geminiRetryService = new GeminiRetryService();
@@ -34,6 +34,9 @@ export type {
34
34
  ExecutionOptions,
35
35
  } from "./generation-executor";
36
36
 
37
+ // Retry service types
38
+ export type { RetryOptions } from "./gemini-retry.service";
39
+
37
40
  // Re-export types from generation-content for convenience
38
41
  export type {
39
42
  IAIProvider,
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Telemetry Hooks
3
+ * Allows applications to monitor and log AI operations
4
+ */
5
+
6
+ declare const __DEV__: boolean;
7
+
8
+ export interface TelemetryEvent {
9
+ type: "request" | "response" | "error" | "retry";
10
+ timestamp: number;
11
+ model?: string;
12
+ feature?: string;
13
+ duration?: number;
14
+ metadata?: Record<string, unknown>;
15
+ }
16
+
17
+ export type TelemetryListener = (event: TelemetryEvent) => void;
18
+
19
+ class TelemetryHooks {
20
+ private listeners: TelemetryListener[] = [];
21
+
22
+ /**
23
+ * Register a telemetry listener
24
+ */
25
+ subscribe(listener: TelemetryListener): () => void {
26
+ this.listeners.push(listener);
27
+
28
+ return () => {
29
+ const index = this.listeners.indexOf(listener);
30
+ if (index > -1) {
31
+ this.listeners.splice(index, 1);
32
+ }
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Emit a telemetry event to all listeners
38
+ */
39
+ emit(event: TelemetryEvent): void {
40
+ for (const listener of this.listeners) {
41
+ try {
42
+ listener(event);
43
+ } catch (error) {
44
+ // Prevent telemetry errors from breaking the app
45
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
46
+ // eslint-disable-next-line no-console
47
+ console.error("[Telemetry] Listener error:", error);
48
+ }
49
+ }
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Log request start
55
+ */
56
+ logRequest(model: string, feature?: string): number {
57
+ const timestamp = Date.now();
58
+ this.emit({
59
+ type: "request",
60
+ timestamp,
61
+ model,
62
+ feature,
63
+ });
64
+ return timestamp;
65
+ }
66
+
67
+ /**
68
+ * Log response received
69
+ */
70
+ logResponse(model: string, startTime: number, feature?: string, metadata?: Record<string, unknown>): void {
71
+ this.emit({
72
+ type: "response",
73
+ timestamp: Date.now(),
74
+ model,
75
+ feature,
76
+ duration: Date.now() - startTime,
77
+ metadata,
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Log error
83
+ */
84
+ logError(model: string, error: Error, feature?: string): void {
85
+ this.emit({
86
+ type: "error",
87
+ timestamp: Date.now(),
88
+ model,
89
+ feature,
90
+ metadata: {
91
+ error: error.message,
92
+ errorType: error.name,
93
+ },
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Log retry attempt
99
+ */
100
+ logRetry(model: string, attempt: number, feature?: string): void {
101
+ this.emit({
102
+ type: "retry",
103
+ timestamp: Date.now(),
104
+ model,
105
+ feature,
106
+ metadata: { attempt },
107
+ });
108
+ }
109
+
110
+ /**
111
+ * Clear all listeners
112
+ */
113
+ clear(): void {
114
+ this.listeners = [];
115
+ }
116
+
117
+ /**
118
+ * Get current listener count
119
+ */
120
+ getListenerCount(): number {
121
+ return this.listeners.length;
122
+ }
123
+ }
124
+
125
+ export const telemetryHooks = new TelemetryHooks();
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Telemetry Module
3
+ */
4
+
5
+ export { telemetryHooks } from "./TelemetryHooks";
6
+ export type { TelemetryEvent, TelemetryListener } from "./TelemetryHooks";
@@ -21,6 +21,18 @@ export {
21
21
  } from "./image-preparer.util";
22
22
  export type { PreparedImage } from "./image-preparer.util";
23
23
 
24
+ export {
25
+ isValidModel,
26
+ validateModel,
27
+ getSafeModel,
28
+ isTextModel,
29
+ isImageModel,
30
+ isImageEditModel,
31
+ isVideoGenerationModel,
32
+ getModelCategory,
33
+ getAllValidModels,
34
+ } from "./model-validation.util";
35
+
24
36
  // Input builders
25
37
  export {
26
38
  buildSingleImageInput,
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Model Validation Utilities
3
+ * Validates model IDs and configurations
4
+ */
5
+
6
+ import { GEMINI_MODELS, DEFAULT_MODELS } from "../../domain/entities";
7
+
8
+ declare const __DEV__: boolean;
9
+
10
+ /**
11
+ * Known valid model IDs
12
+ */
13
+ const VALID_MODELS = new Set<string>(
14
+ Object.values(GEMINI_MODELS).flatMap((category) => Object.values(category)),
15
+ );
16
+
17
+ /**
18
+ * Check if a model ID is valid
19
+ */
20
+ export function isValidModel(model: string): boolean {
21
+ return VALID_MODELS.has(model);
22
+ }
23
+
24
+ /**
25
+ * Validate model ID and throw if invalid
26
+ */
27
+ export function validateModel(model: string): void {
28
+ if (!model) {
29
+ throw new Error("Model ID cannot be empty");
30
+ }
31
+
32
+ if (!isValidModel(model)) {
33
+ throw new Error(
34
+ `Invalid model ID: ${model}. Valid models: ${Array.from(VALID_MODELS).join(", ")}`,
35
+ );
36
+ }
37
+
38
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
39
+ // eslint-disable-next-line no-console
40
+ console.log("[ModelValidation] Model validated:", model);
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Get a safe model ID (fallback to default if invalid)
46
+ */
47
+ export function getSafeModel(model: string | undefined, defaultType: keyof typeof DEFAULT_MODELS): string {
48
+ if (!model) {
49
+ return DEFAULT_MODELS[defaultType];
50
+ }
51
+
52
+ if (!isValidModel(model)) {
53
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
54
+ // eslint-disable-next-line no-console
55
+ console.warn(`[ModelValidation] Invalid model "${model}", falling back to ${DEFAULT_MODELS[defaultType]}`);
56
+ }
57
+ return DEFAULT_MODELS[defaultType];
58
+ }
59
+
60
+ return model;
61
+ }
62
+
63
+ /**
64
+ * Check if model is a text generation model
65
+ */
66
+ export function isTextModel(model: string): boolean {
67
+ return Object.values(GEMINI_MODELS.TEXT).includes(model as (typeof GEMINI_MODELS.TEXT)[keyof typeof GEMINI_MODELS.TEXT]);
68
+ }
69
+
70
+ /**
71
+ * Check if model is an image generation model
72
+ */
73
+ export function isImageModel(model: string): boolean {
74
+ return Object.values(GEMINI_MODELS.TEXT_TO_IMAGE).includes(model as (typeof GEMINI_MODELS.TEXT_TO_IMAGE)[keyof typeof GEMINI_MODELS.TEXT_TO_IMAGE]);
75
+ }
76
+
77
+ /**
78
+ * Check if model is an image editing model
79
+ */
80
+ export function isImageEditModel(model: string): boolean {
81
+ return Object.values(GEMINI_MODELS.IMAGE_EDIT).includes(model as (typeof GEMINI_MODELS.IMAGE_EDIT)[keyof typeof GEMINI_MODELS.IMAGE_EDIT]);
82
+ }
83
+
84
+ /**
85
+ * Check if model is a video generation model
86
+ */
87
+ export function isVideoGenerationModel(model: string): boolean {
88
+ return Object.values(GEMINI_MODELS.VIDEO_GENERATION).includes(model as (typeof GEMINI_MODELS.VIDEO_GENERATION)[keyof typeof GEMINI_MODELS.VIDEO_GENERATION]);
89
+ }
90
+
91
+ /**
92
+ * Get model category
93
+ */
94
+ export function getModelCategory(model: string): string | null {
95
+ if (isTextModel(model)) return "text";
96
+ if (isImageModel(model)) return "text-to-image";
97
+ if (isImageEditModel(model)) return "image-edit";
98
+ if (isVideoGenerationModel(model)) return "video-generation";
99
+ return null;
100
+ }
101
+
102
+ /**
103
+ * Get all valid model IDs
104
+ */
105
+ export function getAllValidModels(): readonly string[] {
106
+ return Array.from(VALID_MODELS);
107
+ }