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

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.27",
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
@@ -1,20 +1,9 @@
1
1
  /**
2
2
  * @umituz/react-native-ai-gemini-provider
3
3
  * Google Gemini AI provider for React Native applications
4
- *
5
- * Usage:
6
- * import {
7
- * geminiClientService,
8
- * geminiProviderService,
9
- * useGemini,
10
- * mapGeminiError,
11
- * } from '@umituz/react-native-ai-gemini-provider';
12
4
  */
13
5
 
14
- // =============================================================================
15
- // DOMAIN LAYER - Types
16
- // =============================================================================
17
-
6
+ // Domain Types
18
7
  export type {
19
8
  GeminiConfig,
20
9
  GeminiGenerationConfig,
@@ -42,30 +31,14 @@ export type {
42
31
  VideoResolution,
43
32
  VideoOperationStatus,
44
33
  VeoOperation,
45
- } from "./domain/entities";
46
-
47
- export { GeminiErrorType } from "./domain/entities";
48
-
49
- export type {
50
34
  GeminiErrorInfo,
51
35
  GeminiApiError,
36
+ ResponseModality,
52
37
  } from "./domain/entities";
53
38
 
54
- export { GeminiError } from "./domain/entities";
55
-
56
- // Model Constants
57
- export {
58
- GEMINI_MODELS,
59
- DEFAULT_MODELS,
60
- RESPONSE_MODALITIES,
61
- } from "./domain/entities";
62
-
63
- export type { ResponseModality } from "./domain/entities";
64
-
65
- // =============================================================================
66
- // DOMAIN LAYER - Feature Models
67
- // =============================================================================
39
+ export { GeminiErrorType, GeminiError, GEMINI_MODELS, DEFAULT_MODELS, RESPONSE_MODALITIES } from "./domain/entities";
68
40
 
41
+ // Feature Models
69
42
  export {
70
43
  GEMINI_IMAGE_FEATURE_MODELS,
71
44
  GEMINI_VIDEO_FEATURE_MODELS,
@@ -74,14 +47,9 @@ export {
74
47
  getAllFeatureModels,
75
48
  } from "./domain/constants";
76
49
 
77
- export type {
78
- FeatureModelConfig,
79
- } from "./domain/constants";
80
-
81
- // =============================================================================
82
- // INFRASTRUCTURE LAYER - Services
83
- // =============================================================================
50
+ export type { FeatureModelConfig } from "./domain/constants";
84
51
 
52
+ // Services
85
53
  export {
86
54
  geminiClientCoreService,
87
55
  geminiRetryService,
@@ -106,18 +74,31 @@ export type {
106
74
  GenerationInput,
107
75
  GenerationResult,
108
76
  ExecutionOptions,
77
+ RetryOptions,
109
78
  } from "./infrastructure/services";
110
79
 
111
- // =============================================================================
112
- // INFRASTRUCTURE LAYER - Utils
113
- // =============================================================================
114
-
80
+ // Utils
115
81
  export {
116
82
  mapGeminiError,
117
83
  isGeminiErrorRetryable,
118
84
  categorizeGeminiError,
119
85
  createGeminiError,
120
- // Input builders
86
+ isValidModel,
87
+ validateModel,
88
+ getSafeModel,
89
+ isTextModel,
90
+ isImageModel,
91
+ isImageEditModel,
92
+ isVideoGenerationModel,
93
+ getModelCategory,
94
+ getAllValidModels,
95
+ measureAsync,
96
+ measureSync,
97
+ debounce,
98
+ throttle,
99
+ PerformanceTimer,
100
+ PerformanceTracker,
101
+ performanceTracker,
121
102
  buildSingleImageInput,
122
103
  buildDualImageInput,
123
104
  buildUpscaleInput,
@@ -134,31 +115,41 @@ export {
134
115
  } from "./infrastructure/utils";
135
116
 
136
117
  export type {
137
- UpscaleOptions as GeminiUpscaleOptions,
138
- PhotoRestoreOptions as GeminiPhotoRestoreOptions,
139
- FaceSwapOptions as GeminiFaceSwapOptions,
140
- AnimeSelfieOptions as GeminiAnimeSelfieOptions,
141
- RemoveBackgroundOptions as GeminiRemoveBackgroundOptions,
142
- RemoveObjectOptions as GeminiRemoveObjectOptions,
143
- ReplaceBackgroundOptions as GeminiReplaceBackgroundOptions,
144
- VideoFromImageOptions as GeminiVideoFromImageOptions,
118
+ PreparedImage,
119
+ UpscaleOptions,
120
+ PhotoRestoreOptions,
121
+ FaceSwapOptions,
122
+ AnimeSelfieOptions,
123
+ RemoveBackgroundOptions,
124
+ RemoveObjectOptions,
125
+ ReplaceBackgroundOptions,
126
+ VideoFromImageOptions,
127
+ PerformanceMetrics,
145
128
  } from "./infrastructure/utils";
146
129
 
147
- // =============================================================================
148
- // PRESENTATION LAYER - Hooks
149
- // =============================================================================
150
-
130
+ // Hooks
151
131
  export { useGemini } from "./presentation/hooks";
152
132
 
133
+ export type { UseGeminiOptions, UseGeminiReturn } from "./presentation/hooks";
134
+
135
+ // Telemetry
136
+ export { telemetryHooks } from "./infrastructure/telemetry";
137
+ export type { TelemetryEvent, TelemetryListener } from "./infrastructure/telemetry";
138
+
139
+ // Interceptors
140
+ export { requestInterceptors, responseInterceptors } from "./infrastructure/interceptors";
153
141
  export type {
154
- UseGeminiOptions,
155
- UseGeminiReturn,
156
- } from "./presentation/hooks";
142
+ RequestContext,
143
+ RequestInterceptor,
144
+ ResponseContext,
145
+ ResponseInterceptor,
146
+ } from "./infrastructure/interceptors";
157
147
 
158
- // =============================================================================
159
- // PROVIDER CONFIGURATION - Tier-based Setup
160
- // =============================================================================
148
+ // Cache
149
+ export { SimpleCache, modelSelectionCache } from "./infrastructure/cache";
150
+ export type { CacheOptions } from "./infrastructure/cache";
161
151
 
152
+ // Provider Config
162
153
  export {
163
154
  providerFactory,
164
155
  resolveProviderConfig,
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Simple LRU Cache
3
+ * Least Recently Used cache for performance optimization
4
+ */
5
+
6
+ declare const __DEV__: boolean;
7
+
8
+ interface CacheEntry<V> {
9
+ value: V;
10
+ expiresAt: number;
11
+ }
12
+
13
+ export interface CacheOptions {
14
+ maxSize?: number;
15
+ ttl?: number; // Time to live in milliseconds
16
+ }
17
+
18
+ export class SimpleCache<K, V> {
19
+ private cache = new Map<K, CacheEntry<V>>();
20
+ private maxSize: number;
21
+ private ttl: number;
22
+
23
+ constructor(options: CacheOptions = {}) {
24
+ this.maxSize = options.maxSize ?? 100;
25
+ this.ttl = options.ttl ?? 5 * 60 * 1000; // 5 minutes default
26
+ }
27
+
28
+ /**
29
+ * Set a value in the cache
30
+ */
31
+ set(key: K, value: V, customTtl?: number): void {
32
+ // Remove oldest entry if at capacity
33
+ if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
34
+ const firstKey = this.cache.keys().next().value;
35
+ if (firstKey !== undefined) {
36
+ this.cache.delete(firstKey);
37
+ }
38
+ }
39
+
40
+ const ttl = customTtl ?? this.ttl;
41
+ this.cache.set(key, {
42
+ value,
43
+ expiresAt: Date.now() + ttl,
44
+ });
45
+ }
46
+
47
+ /**
48
+ * Get a value from the cache
49
+ * Returns undefined if not found or expired
50
+ */
51
+ get(key: K): V | undefined {
52
+ const entry = this.cache.get(key);
53
+
54
+ if (!entry) {
55
+ return undefined;
56
+ }
57
+
58
+ // Check if expired
59
+ if (Date.now() > entry.expiresAt) {
60
+ this.cache.delete(key);
61
+ return undefined;
62
+ }
63
+
64
+ // Move to end (most recently used)
65
+ this.cache.delete(key);
66
+ this.cache.set(key, entry);
67
+
68
+ return entry.value;
69
+ }
70
+
71
+ /**
72
+ * Check if a key exists and is not expired
73
+ */
74
+ has(key: K): boolean {
75
+ return this.get(key) !== undefined;
76
+ }
77
+
78
+ /**
79
+ * Delete a specific key
80
+ */
81
+ delete(key: K): boolean {
82
+ return this.cache.delete(key);
83
+ }
84
+
85
+ /**
86
+ * Clear all entries
87
+ */
88
+ clear(): void {
89
+ this.cache.clear();
90
+ }
91
+
92
+ /**
93
+ * Get current cache size
94
+ */
95
+ size(): number {
96
+ // Clean expired entries first
97
+ this.cleanExpired();
98
+ return this.cache.size;
99
+ }
100
+
101
+ /**
102
+ * Remove all expired entries
103
+ */
104
+ private cleanExpired(): void {
105
+ const now = Date.now();
106
+ for (const [key, entry] of this.cache.entries()) {
107
+ if (now > entry.expiresAt) {
108
+ this.cache.delete(key);
109
+ }
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Get all valid keys (not expired)
115
+ */
116
+ keys(): K[] {
117
+ this.cleanExpired();
118
+ return Array.from(this.cache.keys());
119
+ }
120
+
121
+ /**
122
+ * Get cache statistics
123
+ */
124
+ getStats(): {
125
+ size: number;
126
+ maxSize: number;
127
+ keys: K[];
128
+ } {
129
+ return {
130
+ size: this.size(),
131
+ maxSize: this.maxSize,
132
+ keys: this.keys(),
133
+ };
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Global model selection cache
139
+ * Caches model IDs for features to avoid repeated lookups
140
+ */
141
+ class ModelSelectionCache {
142
+ private cache = new SimpleCache<string, string>({
143
+ maxSize: 50,
144
+ ttl: 10 * 60 * 1000, // 10 minutes
145
+ });
146
+
147
+ set(feature: string, model: string): void {
148
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
149
+ // eslint-disable-next-line no-console
150
+ console.log("[ModelSelectionCache] Caching model:", { feature, model });
151
+ }
152
+ this.cache.set(feature, model);
153
+ }
154
+
155
+ get(feature: string): string | undefined {
156
+ const model = this.cache.get(feature);
157
+ if (model && typeof __DEV__ !== "undefined" && __DEV__) {
158
+ // eslint-disable-next-line no-console
159
+ console.log("[ModelSelectionCache] Cache hit:", { feature, model });
160
+ }
161
+ return model;
162
+ }
163
+
164
+ clear(): void {
165
+ this.cache.clear();
166
+ }
167
+
168
+ getStats(): ReturnType<SimpleCache<string, string>["getStats"]> {
169
+ return this.cache.getStats();
170
+ }
171
+ }
172
+
173
+ export const modelSelectionCache = new ModelSelectionCache();
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Cache Module
3
+ * Simple LRU cache for performance optimization
4
+ */
5
+
6
+ export { SimpleCache, modelSelectionCache } from "./SimpleCache";
7
+ export type { CacheOptions } from "./SimpleCache";
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Request Interceptors
3
+ * Allows applications to modify requests before they're sent
4
+ */
5
+
6
+ export interface RequestContext {
7
+ model: string;
8
+ feature?: string;
9
+ payload: Record<string, unknown>;
10
+ timestamp: number;
11
+ }
12
+
13
+ export type RequestInterceptor = (context: RequestContext) => RequestContext | Promise<RequestContext>;
14
+
15
+ class RequestInterceptors {
16
+ private interceptors: RequestInterceptor[] = [];
17
+
18
+ /**
19
+ * Register a request interceptor
20
+ * Interceptors are called in order (first registered = first called)
21
+ */
22
+ use(interceptor: RequestInterceptor): () => void {
23
+ this.interceptors.push(interceptor);
24
+
25
+ // Return unsubscribe function
26
+ return () => {
27
+ const index = this.interceptors.indexOf(interceptor);
28
+ if (index > -1) {
29
+ this.interceptors.splice(index, 1);
30
+ }
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Apply all interceptors to a request context
36
+ */
37
+ async apply(context: RequestContext): Promise<RequestContext> {
38
+ let result = context;
39
+
40
+ for (const interceptor of this.interceptors) {
41
+ try {
42
+ result = await interceptor(result);
43
+ } catch (error) {
44
+ // Interceptor error should fail the request
45
+ throw new Error(`Request interceptor failed: ${error instanceof Error ? error.message : String(error)}`);
46
+ }
47
+ }
48
+
49
+ return result;
50
+ }
51
+
52
+ /**
53
+ * Clear all interceptors
54
+ */
55
+ clear(): void {
56
+ this.interceptors = [];
57
+ }
58
+
59
+ /**
60
+ * Get interceptor count
61
+ */
62
+ count(): number {
63
+ return this.interceptors.length;
64
+ }
65
+ }
66
+
67
+ export const requestInterceptors = new RequestInterceptors();
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Response Interceptors
3
+ * Allows applications to modify responses after they're received
4
+ */
5
+
6
+ export interface ResponseContext<T = unknown> {
7
+ model: string;
8
+ feature?: string;
9
+ data: T;
10
+ duration: number;
11
+ timestamp: number;
12
+ }
13
+
14
+ export type ResponseInterceptor<T = unknown> = (
15
+ context: ResponseContext<T>,
16
+ ) => ResponseContext<T> | Promise<ResponseContext<T>>;
17
+
18
+ class ResponseInterceptors {
19
+ private interceptors: Array<ResponseInterceptor<unknown>> = [];
20
+
21
+ /**
22
+ * Register a response interceptor
23
+ * Interceptors are called in reverse order (last registered = first called)
24
+ */
25
+ use<T = unknown>(interceptor: ResponseInterceptor<T>): () => void {
26
+ this.interceptors.push(interceptor as ResponseInterceptor<unknown>);
27
+
28
+ // Return unsubscribe function
29
+ return () => {
30
+ const index = this.interceptors.indexOf(interceptor as ResponseInterceptor<unknown>);
31
+ if (index > -1) {
32
+ this.interceptors.splice(index, 1);
33
+ }
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Apply all interceptors to a response context
39
+ */
40
+ async apply<T>(context: ResponseContext<T>): Promise<ResponseContext<T>> {
41
+ let result: ResponseContext<unknown> = context;
42
+
43
+ // Apply in reverse order (last added = first processed)
44
+ for (let i = this.interceptors.length - 1; i >= 0; i--) {
45
+ const interceptor = this.interceptors[i];
46
+ try {
47
+ result = await interceptor(result);
48
+ } catch (error) {
49
+ // Interceptor error should fail the response processing
50
+ throw new Error(`Response interceptor failed: ${error instanceof Error ? error.message : String(error)}`);
51
+ }
52
+ }
53
+
54
+ return result as ResponseContext<T>;
55
+ }
56
+
57
+ /**
58
+ * Clear all interceptors
59
+ */
60
+ clear(): void {
61
+ this.interceptors = [];
62
+ }
63
+
64
+ /**
65
+ * Get interceptor count
66
+ */
67
+ count(): number {
68
+ return this.interceptors.length;
69
+ }
70
+ }
71
+
72
+ export const responseInterceptors = new ResponseInterceptors();
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Interceptors Module
3
+ * Allows applications to modify requests and responses
4
+ */
5
+
6
+ export { requestInterceptors } from "./RequestInterceptors";
7
+ export { responseInterceptors } from "./ResponseInterceptors";
8
+
9
+ export type {
10
+ RequestContext,
11
+ RequestInterceptor,
12
+ } from "./RequestInterceptors";
13
+
14
+ export type {
15
+ ResponseContext,
16
+ ResponseInterceptor,
17
+ } from "./ResponseInterceptors";
@@ -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,29 @@ 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
+
36
+ export {
37
+ measureAsync,
38
+ measureSync,
39
+ debounce,
40
+ throttle,
41
+ PerformanceTimer,
42
+ PerformanceTracker,
43
+ performanceTracker,
44
+ } from "./performance.util";
45
+ export type { PerformanceMetrics } from "./performance.util";
46
+
24
47
  // Input builders
25
48
  export {
26
49
  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
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Performance Utilities
3
+ * Tools for measuring and optimizing performance
4
+ */
5
+
6
+ declare const __DEV__: boolean;
7
+
8
+ export interface PerformanceMetrics {
9
+ duration: number;
10
+ timestamp: number;
11
+ metadata?: Record<string, unknown>;
12
+ }
13
+
14
+ export class PerformanceTimer {
15
+ private startTime: number;
16
+ private endTime?: number;
17
+ private metadata?: Record<string, unknown>;
18
+
19
+ constructor(metadata?: Record<string, unknown>) {
20
+ this.startTime = Date.now();
21
+ this.metadata = metadata;
22
+ }
23
+
24
+ stop(): number {
25
+ this.endTime = Date.now();
26
+ return this.duration;
27
+ }
28
+
29
+ get duration(): number {
30
+ const end = this.endTime ?? Date.now();
31
+ return end - this.startTime;
32
+ }
33
+
34
+ getMetrics(): PerformanceMetrics {
35
+ return { duration: this.duration, timestamp: this.startTime, metadata: this.metadata };
36
+ }
37
+
38
+ get isRunning(): boolean {
39
+ return this.endTime === undefined;
40
+ }
41
+ }
42
+
43
+ export async function measureAsync<T>(
44
+ operation: () => Promise<T>,
45
+ metadata?: Record<string, unknown>,
46
+ ): Promise<{ result: T; duration: number }> {
47
+ const timer = new PerformanceTimer(metadata);
48
+ try {
49
+ const result = await operation();
50
+ const duration = timer.stop();
51
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
52
+ // eslint-disable-next-line no-console
53
+ console.log("[Performance] Operation completed:", { duration: `${duration}ms`, metadata });
54
+ }
55
+ return { result, duration };
56
+ } catch (error) {
57
+ timer.stop();
58
+ throw error;
59
+ }
60
+ }
61
+
62
+ export function measureSync<T>(
63
+ operation: () => T,
64
+ metadata?: Record<string, unknown>,
65
+ ): { result: T; duration: number } {
66
+ const timer = new PerformanceTimer(metadata);
67
+ try {
68
+ const result = operation();
69
+ const duration = timer.stop();
70
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
71
+ // eslint-disable-next-line no-console
72
+ console.log("[Performance] Operation completed:", { duration: `${duration}ms`, metadata });
73
+ }
74
+ return { result, duration };
75
+ } catch (error) {
76
+ timer.stop();
77
+ throw error;
78
+ }
79
+ }
80
+
81
+ export function debounce<T extends (...args: never[]) => unknown>(
82
+ func: T,
83
+ wait: number,
84
+ ): (...args: Parameters<T>) => void {
85
+ let timeout: ReturnType<typeof setTimeout> | undefined;
86
+ return (...args: Parameters<T>) => {
87
+ const later = () => {
88
+ clearTimeout(timeout);
89
+ func(...args);
90
+ };
91
+ clearTimeout(timeout);
92
+ timeout = setTimeout(later, wait);
93
+ };
94
+ }
95
+
96
+ export function throttle<T extends (...args: never[]) => unknown>(
97
+ func: T,
98
+ limit: number,
99
+ ): (...args: Parameters<T>) => void {
100
+ let inThrottle: boolean;
101
+ return (...args: Parameters<T>) => {
102
+ if (!inThrottle) {
103
+ func(...args);
104
+ inThrottle = true;
105
+ setTimeout(() => (inThrottle = false), limit);
106
+ }
107
+ };
108
+ }
109
+
110
+ export class PerformanceTracker {
111
+ private metrics = new Map<string, number[]>();
112
+
113
+ record(operation: string, duration: number): void {
114
+ if (!this.metrics.has(operation)) {
115
+ this.metrics.set(operation, []);
116
+ }
117
+ this.metrics.get(operation)!.push(duration);
118
+ }
119
+
120
+ getStats(operation: string): { count: number; avg: number; min: number; max: number } | null {
121
+ const durations = this.metrics.get(operation);
122
+ if (!durations || durations.length === 0) return null;
123
+ return {
124
+ count: durations.length,
125
+ avg: durations.reduce((a, b) => a + b, 0) / durations.length,
126
+ min: Math.min(...durations),
127
+ max: Math.max(...durations),
128
+ };
129
+ }
130
+
131
+ getAllStats(): Record<string, ReturnType<PerformanceTracker["getStats"]>> {
132
+ const stats: Record<string, ReturnType<PerformanceTracker["getStats"]>> = {};
133
+ for (const operation of this.metrics.keys()) {
134
+ stats[operation] = this.getStats(operation);
135
+ }
136
+ return stats;
137
+ }
138
+
139
+ clear(): void {
140
+ this.metrics.clear();
141
+ }
142
+ }
143
+
144
+ export const performanceTracker = new PerformanceTracker();
145
+