@umituz/react-native-ai-gemini-provider 2.0.13 → 2.0.14

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.
Files changed (50) hide show
  1. package/package.json +1 -1
  2. package/src/domain/entities/error.types.ts +0 -17
  3. package/src/domain/entities/gemini.types.ts +1 -37
  4. package/src/domain/entities/models.ts +0 -31
  5. package/src/index.ts +8 -24
  6. package/src/infrastructure/interceptors/RequestInterceptors.ts +20 -6
  7. package/src/infrastructure/interceptors/ResponseInterceptors.ts +20 -6
  8. package/src/infrastructure/services/gemini-client-core.service.ts +25 -32
  9. package/src/infrastructure/services/gemini-provider.ts +31 -15
  10. package/src/infrastructure/services/gemini-streaming.service.ts +0 -4
  11. package/src/infrastructure/services/gemini-structured-text.service.ts +8 -5
  12. package/src/infrastructure/services/gemini-text-generation.service.ts +20 -51
  13. package/src/infrastructure/services/index.ts +2 -16
  14. package/src/infrastructure/telemetry/TelemetryHooks.ts +23 -10
  15. package/src/infrastructure/utils/async-state.util.ts +0 -7
  16. package/src/infrastructure/utils/error-mapper.util.ts +13 -8
  17. package/src/infrastructure/utils/gemini-data-transformer.util.ts +12 -9
  18. package/src/infrastructure/utils/performance.util.ts +4 -54
  19. package/src/infrastructure/utils/rate-limiter.util.ts +12 -8
  20. package/src/presentation/hooks/use-gemini.ts +91 -24
  21. package/src/providers/ProviderConfig.ts +0 -32
  22. package/src/providers/ProviderFactory.ts +24 -37
  23. package/src/providers/index.ts +2 -7
  24. package/src/domain/README.md +0 -232
  25. package/src/domain/constants/index.ts +0 -5
  26. package/src/domain/entities/README.md +0 -238
  27. package/src/infrastructure/README.md +0 -252
  28. package/src/infrastructure/cache/CACHE_SYSTEM.md +0 -213
  29. package/src/infrastructure/cache/README.md +0 -213
  30. package/src/infrastructure/cache/SimpleCache.ts +0 -173
  31. package/src/infrastructure/cache/index.ts +0 -7
  32. package/src/infrastructure/content/ContentBuilder.ts +0 -24
  33. package/src/infrastructure/content/README.md +0 -175
  34. package/src/infrastructure/interceptors/README.md +0 -226
  35. package/src/infrastructure/interceptors/REQUEST_INTERCEPTORS.md +0 -171
  36. package/src/infrastructure/job/JOB_MANAGER.md +0 -174
  37. package/src/infrastructure/job/JobManager.ts +0 -114
  38. package/src/infrastructure/job/README.md +0 -194
  39. package/src/infrastructure/response/README.md +0 -187
  40. package/src/infrastructure/response/RESPONSE_FORMATTER.md +0 -185
  41. package/src/infrastructure/response/ResponseFormatter.ts +0 -58
  42. package/src/infrastructure/services/generation-executor.ts +0 -71
  43. package/src/infrastructure/services/job-processor.ts +0 -58
  44. package/src/infrastructure/services/provider-initializer.ts +0 -31
  45. package/src/infrastructure/telemetry/README.md +0 -203
  46. package/src/infrastructure/telemetry/TELEMETRY_SYSTEM.md +0 -200
  47. package/src/presentation/README.md +0 -187
  48. package/src/presentation/hooks/README.md +0 -188
  49. package/src/presentation/hooks/USE_GEMINI_HOOK.md +0 -226
  50. package/src/providers/README.md +0 -247
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-gemini-provider",
3
- "version": "2.0.13",
3
+ "version": "2.0.14",
4
4
  "description": "Google Gemini AI text generation provider for React Native applications",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -1,8 +1,3 @@
1
- /**
2
- * Gemini Error Types
3
- * Error classification for Gemini API
4
- */
5
-
6
1
  export enum GeminiErrorType {
7
2
  NETWORK = "NETWORK",
8
3
  RATE_LIMIT = "RATE_LIMIT",
@@ -38,9 +33,6 @@ export interface GeminiApiError {
38
33
  };
39
34
  }
40
35
 
41
- /**
42
- * Custom error class for Gemini API errors
43
- */
44
36
  export class GeminiError extends Error {
45
37
  readonly type: GeminiErrorType;
46
38
  readonly retryable: boolean;
@@ -61,23 +53,14 @@ export class GeminiError extends Error {
61
53
  }
62
54
  }
63
55
 
64
- /**
65
- * Check if error is retryable
66
- */
67
56
  isRetryable(): boolean {
68
57
  return this.retryable;
69
58
  }
70
59
 
71
- /**
72
- * Get error type
73
- */
74
60
  getErrorType(): GeminiErrorType {
75
61
  return this.type;
76
62
  }
77
63
 
78
- /**
79
- * Create GeminiError from unknown error
80
- */
81
64
  static fromError(_error: unknown, info: GeminiErrorInfo): GeminiError {
82
65
  return new GeminiError(info);
83
66
  }
@@ -1,28 +1,16 @@
1
- /**
2
- * Gemini Provider Types
3
- * Configuration and response types for Google Gemini AI
4
- */
5
-
6
1
  import type { GenerationConfig } from "@google/generative-ai";
7
2
 
8
3
  export interface GeminiConfig {
9
4
  apiKey: string;
10
5
  baseUrl?: string;
11
6
  defaultTimeoutMs?: number;
12
- /** Model used for text generation (default: gemini-2.5-flash-lite) */
13
7
  textModel?: string;
14
8
  }
15
9
 
16
10
  export type GeminiGenerationConfig = Omit<GenerationConfig, "responseSchema"> & {
17
- /** Response schema for structured JSON output - compatible with Google SDK */
18
11
  responseSchema?: GenerationConfig["responseSchema"];
19
12
  };
20
13
 
21
- export interface GeminiSafetySettings {
22
- category: GeminiHarmCategory;
23
- threshold: GeminiHarmBlockThreshold;
24
- }
25
-
26
14
  export type GeminiHarmCategory =
27
15
  | "HARM_CATEGORY_HARASSMENT"
28
16
  | "HARM_CATEGORY_HATE_SPEECH"
@@ -40,18 +28,10 @@ export interface GeminiContent {
40
28
  role?: "user" | "model";
41
29
  }
42
30
 
43
- export type GeminiPart =
44
- | { text: string };
45
-
46
- export interface GeminiRequest {
47
- contents: GeminiContent[];
48
- generationConfig?: GeminiGenerationConfig;
49
- safetySettings?: GeminiSafetySettings[];
50
- }
31
+ export type GeminiPart = { text: string };
51
32
 
52
33
  export interface GeminiResponse {
53
34
  candidates?: GeminiCandidate[];
54
- promptFeedback?: GeminiPromptFeedback;
55
35
  usageMetadata?: GeminiUsageMetadata;
56
36
  }
57
37
 
@@ -59,7 +39,6 @@ export interface GeminiCandidate {
59
39
  content: GeminiContent;
60
40
  finishReason?: GeminiFinishReason;
61
41
  safetyRatings?: GeminiSafetyRating[];
62
- index?: number;
63
42
  }
64
43
 
65
44
  export type GeminiFinishReason =
@@ -76,24 +55,9 @@ export interface GeminiSafetyRating {
76
55
  blocked?: boolean;
77
56
  }
78
57
 
79
- export interface GeminiPromptFeedback {
80
- blockReason?: "BLOCK_REASON_UNSPECIFIED" | "SAFETY" | "OTHER";
81
- safetyRatings?: GeminiSafetyRating[];
82
- }
83
-
84
58
  export interface GeminiUsageMetadata {
85
59
  promptTokenCount?: number;
86
60
  candidatesTokenCount?: number;
87
61
  totalTokenCount?: number;
88
62
  }
89
63
 
90
- export interface GeminiModel {
91
- id: string;
92
- name: string;
93
- displayName: string;
94
- description?: string;
95
- inputTokenLimit?: number;
96
- outputTokenLimit?: number;
97
- supportedCapabilities?: string[];
98
- }
99
-
@@ -1,44 +1,13 @@
1
- /**
2
- * Gemini Model Constants
3
- * Centralized model configuration for all AI operations
4
- * Updated: 2026-01 with latest pricing and free tier info
5
- */
6
-
7
- /**
8
- * Available Gemini models
9
- * Pricing (per 1M tokens):
10
- * - Flash-Lite: $0.10 input / $0.40 output (FREE: 1000 req/day)
11
- */
12
1
  export const GEMINI_MODELS = {
13
- // Text generation models (ordered by cost: cheapest first)
14
2
  TEXT: {
15
- /** Most cost-effective, 1000 free requests/day */
16
3
  FLASH_LITE: "gemini-2.5-flash-lite",
17
4
  },
18
5
  } as const;
19
6
 
20
- /**
21
- * Default models for each operation type
22
- * Optimized for cost-effectiveness while maintaining quality
23
- * Using Flash-Lite as default for best free tier (1000 req/day)
24
- */
25
7
  export const DEFAULT_MODELS = {
26
- /** Flash-Lite: Cheapest & highest free tier (1000/day) */
27
8
  TEXT: GEMINI_MODELS.TEXT.FLASH_LITE,
28
9
  } as const;
29
10
 
30
- /**
31
- * Model pricing information (per 1M tokens)
32
- */
33
11
  export const MODEL_PRICING = {
34
12
  [GEMINI_MODELS.TEXT.FLASH_LITE]: { input: 0.10, output: 0.40, freePerDay: 1000 },
35
13
  } as const;
36
-
37
- /**
38
- * Response modalities for different generation types
39
- */
40
- export const RESPONSE_MODALITIES = {
41
- TEXT_ONLY: ["TEXT"] as const,
42
- } as const;
43
-
44
- export type ResponseModality = "TEXT";
package/src/index.ts CHANGED
@@ -8,31 +8,25 @@
8
8
  export type {
9
9
  GeminiConfig,
10
10
  GeminiGenerationConfig,
11
- GeminiSafetySettings,
12
11
  GeminiHarmCategory,
13
12
  GeminiHarmBlockThreshold,
14
13
  GeminiContent,
15
14
  GeminiPart,
16
- GeminiRequest,
17
15
  GeminiResponse,
18
16
  GeminiCandidate,
19
17
  GeminiFinishReason,
20
18
  GeminiSafetyRating,
21
- GeminiPromptFeedback,
22
19
  GeminiUsageMetadata,
23
- GeminiModel,
24
20
  GeminiErrorInfo,
25
21
  GeminiApiError,
26
- ResponseModality,
27
22
  } from "./domain/entities";
28
23
 
29
- export { GeminiErrorType, GeminiError, GEMINI_MODELS, DEFAULT_MODELS, MODEL_PRICING, RESPONSE_MODALITIES } from "./domain/entities";
24
+ export { GeminiErrorType, GeminiError, GEMINI_MODELS, DEFAULT_MODELS, MODEL_PRICING } from "./domain/entities";
30
25
 
31
26
  // Services
32
27
  export {
33
28
  geminiClientCoreService,
34
29
  geminiTextGenerationService,
35
- geminiTextService,
36
30
  geminiStructuredTextService,
37
31
  geminiStreamingService,
38
32
  geminiProviderService,
@@ -40,12 +34,7 @@ export {
40
34
  GeminiProvider,
41
35
  } from "./infrastructure/services";
42
36
 
43
- export type {
44
- GeminiProviderConfig,
45
- GenerationInput,
46
- GenerationResult,
47
- ExecutionOptions,
48
- } from "./infrastructure/services";
37
+ export type { GeminiProviderConfig } from "./infrastructure/services";
49
38
 
50
39
  // Utils
51
40
  export {
@@ -58,10 +47,7 @@ export {
58
47
  debounce,
59
48
  throttle,
60
49
  PerformanceTimer,
61
- PerformanceTracker,
62
- performanceTracker,
63
50
  RateLimiter,
64
- rateLimiter,
65
51
  } from "./infrastructure/utils";
66
52
 
67
53
  export type {
@@ -80,23 +66,22 @@ export type { TelemetryEvent, TelemetryListener } from "./infrastructure/telemet
80
66
 
81
67
  // Interceptors
82
68
  export { requestInterceptors, responseInterceptors } from "./infrastructure/interceptors";
69
+
83
70
  export type {
84
71
  RequestContext,
85
72
  RequestInterceptor,
73
+ InterceptorErrorStrategy,
74
+ } from "./infrastructure/interceptors/RequestInterceptors";
75
+
76
+ export type {
86
77
  ResponseContext,
87
78
  ResponseInterceptor,
88
- } from "./infrastructure/interceptors";
89
-
90
- // Cache
91
- export { SimpleCache, modelSelectionCache } from "./infrastructure/cache";
92
- export type { CacheOptions } from "./infrastructure/cache";
79
+ } from "./infrastructure/interceptors/ResponseInterceptors";
93
80
 
94
81
  // Provider Config
95
82
  export {
96
83
  providerFactory,
97
84
  resolveProviderConfig,
98
- getCostOptimizedConfig,
99
- getQualityOptimizedConfig,
100
85
  } from "./providers";
101
86
 
102
87
  export type {
@@ -104,6 +89,5 @@ export type {
104
89
  ProviderPreferences,
105
90
  ProviderConfigInput,
106
91
  ResolvedProviderConfig,
107
- OptimizationStrategy,
108
92
  ProviderFactoryOptions,
109
93
  } from "./providers";
@@ -1,7 +1,3 @@
1
- /**
2
- * Request Interceptors
3
- * Allows applications to modify requests before they're sent
4
- */
5
1
 
6
2
  export interface RequestContext {
7
3
  model: string;
@@ -12,8 +8,11 @@ export interface RequestContext {
12
8
 
13
9
  export type RequestInterceptor = (context: RequestContext) => RequestContext | Promise<RequestContext>;
14
10
 
11
+ export type InterceptorErrorStrategy = "fail" | "skip" | "log";
12
+
15
13
  class RequestInterceptors {
16
14
  private interceptors: RequestInterceptor[] = [];
15
+ private errorStrategy: InterceptorErrorStrategy = "fail";
17
16
 
18
17
  /**
19
18
  * Register a request interceptor
@@ -31,6 +30,13 @@ class RequestInterceptors {
31
30
  };
32
31
  }
33
32
 
33
+ /**
34
+ * Set error handling strategy for interceptors
35
+ */
36
+ setErrorStrategy(strategy: InterceptorErrorStrategy): void {
37
+ this.errorStrategy = strategy;
38
+ }
39
+
34
40
  /**
35
41
  * Apply all interceptors to a request context
36
42
  */
@@ -41,8 +47,16 @@ class RequestInterceptors {
41
47
  try {
42
48
  result = await interceptor(result);
43
49
  } catch (error) {
44
- // Interceptor error should fail the request
45
- throw new Error(`Request interceptor failed: ${error instanceof Error ? error.message : String(error)}`);
50
+ switch (this.errorStrategy) {
51
+ case "fail":
52
+ throw new Error(`Request interceptor failed: ${error instanceof Error ? error.message : String(error)}`);
53
+ case "skip":
54
+ // Skip this interceptor and continue with previous result
55
+ break;
56
+ case "log":
57
+ // Silently ignore but continue
58
+ break;
59
+ }
46
60
  }
47
61
  }
48
62
 
@@ -1,7 +1,3 @@
1
- /**
2
- * Response Interceptors
3
- * Allows applications to modify responses after they're received
4
- */
5
1
 
6
2
  export interface ResponseContext<T = unknown> {
7
3
  model: string;
@@ -15,8 +11,11 @@ export type ResponseInterceptor<T = unknown> = (
15
11
  context: ResponseContext<T>,
16
12
  ) => ResponseContext<T> | Promise<ResponseContext<T>>;
17
13
 
14
+ export type InterceptorErrorStrategy = "fail" | "skip" | "log";
15
+
18
16
  class ResponseInterceptors {
19
17
  private interceptors: Array<ResponseInterceptor<unknown>> = [];
18
+ private errorStrategy: InterceptorErrorStrategy = "fail";
20
19
 
21
20
  /**
22
21
  * Register a response interceptor
@@ -34,6 +33,13 @@ class ResponseInterceptors {
34
33
  };
35
34
  }
36
35
 
36
+ /**
37
+ * Set error handling strategy for interceptors
38
+ */
39
+ setErrorStrategy(strategy: InterceptorErrorStrategy): void {
40
+ this.errorStrategy = strategy;
41
+ }
42
+
37
43
  /**
38
44
  * Apply all interceptors to a response context
39
45
  */
@@ -46,8 +52,16 @@ class ResponseInterceptors {
46
52
  try {
47
53
  result = await interceptor(result);
48
54
  } catch (error) {
49
- // Interceptor error should fail the response processing
50
- throw new Error(`Response interceptor failed: ${error instanceof Error ? error.message : String(error)}`);
55
+ switch (this.errorStrategy) {
56
+ case "fail":
57
+ throw new Error(`Response interceptor failed: ${error instanceof Error ? error.message : String(error)}`);
58
+ case "skip":
59
+ // Skip this interceptor and continue with previous result
60
+ break;
61
+ case "log":
62
+ // Silently ignore but continue
63
+ break;
64
+ }
51
65
  }
52
66
  }
53
67
 
@@ -1,14 +1,7 @@
1
- /**
2
- * Gemini Client Core Service
3
- * Handles client initialization, configuration, and validation
4
- */
5
-
6
1
  import { GoogleGenerativeAI, type GenerativeModel } from "@google/generative-ai";
7
- import { DEFAULT_MODELS } from "../../domain/entities";
2
+ import { DEFAULT_MODELS, GEMINI_MODELS } from "../../domain/entities";
8
3
  import type { GeminiConfig } from "../../domain/entities";
9
4
 
10
- declare const __DEV__: boolean;
11
-
12
5
  const DEFAULT_CONFIG: Partial<GeminiConfig> = {
13
6
  textModel: DEFAULT_MODELS.TEXT,
14
7
  };
@@ -20,31 +13,12 @@ class GeminiClientCoreService {
20
13
 
21
14
  initialize(config: GeminiConfig): void {
22
15
  if (this.initialized) {
23
- if (typeof __DEV__ !== "undefined" && __DEV__) {
24
- // eslint-disable-next-line no-console
25
- console.log("[GeminiClient] Already initialized, skipping");
26
- }
27
- return;
28
- }
29
-
30
- if (typeof __DEV__ !== "undefined" && __DEV__) {
31
- // eslint-disable-next-line no-console
32
- console.log("[GeminiClient] initialize() called", {
33
- hasApiKey: !!config.apiKey,
34
- textModel: config.textModel,
35
- });
16
+ throw new Error("Gemini client already initialized. Call reset() before re-initializing with new config.");
36
17
  }
37
18
 
38
19
  this.client = new GoogleGenerativeAI(config.apiKey);
39
20
  this.config = { ...DEFAULT_CONFIG, ...config };
40
21
  this.initialized = true;
41
-
42
- if (typeof __DEV__ !== "undefined" && __DEV__) {
43
- // eslint-disable-next-line no-console
44
- console.log("[GeminiClient] initialized successfully", {
45
- textModel: this.config.textModel,
46
- });
47
- }
48
22
  }
49
23
 
50
24
  isInitialized(): boolean {
@@ -61,16 +35,35 @@ class GeminiClientCoreService {
61
35
 
62
36
  validateInitialization(): void {
63
37
  if (!this.client || !this.initialized) {
64
- throw new Error(
65
- "Gemini client not initialized. Call initialize() first.",
66
- );
38
+ throw new Error("Gemini client not initialized. Call initialize() first.");
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Validate model name against known models
44
+ */
45
+ private validateModel(modelName: string): void {
46
+ const knownModels = Object.values(GEMINI_MODELS.TEXT);
47
+ const isValid = knownModels.some((model) => model === modelName);
48
+
49
+ if (!isValid) {
50
+ throw new Error(`Unknown model: "${modelName}". Known models: ${knownModels.join(", ")}`);
67
51
  }
68
52
  }
69
53
 
70
54
  getModel(modelName?: string): GenerativeModel {
71
55
  this.validateInitialization();
56
+
57
+ if (!this.client) {
58
+ throw new Error("Gemini client not available");
59
+ }
60
+
72
61
  const effectiveModel = modelName || this.config?.textModel || DEFAULT_MODELS.TEXT;
73
- return this.client!.getGenerativeModel({ model: effectiveModel });
62
+
63
+ // Validate model name
64
+ this.validateModel(effectiveModel);
65
+
66
+ return this.client.getGenerativeModel({ model: effectiveModel });
74
67
  }
75
68
 
76
69
  reset(): void {
@@ -1,39 +1,37 @@
1
- /**
2
- * Gemini Provider
3
- * Text-only AI provider for Google Gemini
4
- */
5
1
 
6
2
  import type { GeminiConfig } from "../../domain/entities";
7
- import { providerInitializer } from "./provider-initializer";
8
- import { generationExecutor } from "./generation-executor";
3
+ import { geminiClientCoreService } from "./gemini-client-core.service";
4
+ import { geminiTextGenerationService } from "./gemini-text-generation.service";
5
+ import { geminiStructuredTextService } from "./gemini-structured-text.service";
9
6
 
10
7
  export type GeminiProviderConfig = GeminiConfig;
11
8
 
12
- /**
13
- * Gemini Provider - Text Generation Only
14
- * For image/video generation, use FAL Provider instead
15
- */
16
9
  export class GeminiProvider {
17
10
  readonly providerId = "gemini";
18
11
  readonly providerName = "Google Gemini";
19
12
 
20
13
  initialize(config: GeminiProviderConfig): void {
21
- providerInitializer.initialize(config);
14
+ if (geminiClientCoreService.isInitialized()) {
15
+ throw new Error("Provider already initialized. Call reset() before re-initializing with new config.");
16
+ }
17
+ geminiClientCoreService.initialize(config);
22
18
  }
23
19
 
24
20
  isInitialized(): boolean {
25
- return providerInitializer.isInitialized();
21
+ return geminiClientCoreService.isInitialized();
26
22
  }
27
23
 
28
24
  reset(): void {
29
- providerInitializer.reset();
25
+ geminiClientCoreService.reset();
30
26
  }
31
27
 
32
28
  /**
33
29
  * Generate text from prompt
34
30
  */
35
31
  async generateText(prompt: string, model: string): Promise<string> {
36
- return generationExecutor.executeTextGeneration(prompt, model);
32
+ const contents = [{ parts: [{ text: prompt }], role: "user" as const }];
33
+ const response = await geminiTextGenerationService.generateContent(model, contents);
34
+ return this.extractTextFromResponse(response);
37
35
  }
38
36
 
39
37
  /**
@@ -44,7 +42,25 @@ export class GeminiProvider {
44
42
  schema: Record<string, unknown>,
45
43
  model: string,
46
44
  ): Promise<T> {
47
- return generationExecutor.executeStructuredGeneration<T>(prompt, schema, model);
45
+ return geminiStructuredTextService.generateStructuredText<T>(model, prompt, schema);
46
+ }
47
+
48
+ /**
49
+ * Extract text from Gemini response
50
+ */
51
+ private extractTextFromResponse(response: unknown): string {
52
+ const resp = response as {
53
+ candidates?: Array<{
54
+ content: {
55
+ parts: Array<{ text?: string }>;
56
+ };
57
+ }>;
58
+ };
59
+
60
+ return resp.candidates?.[0]?.content.parts
61
+ .filter((p): p is { text: string } => "text" in p && typeof p.text === "string")
62
+ .map((p) => p.text)
63
+ .join("") || "";
48
64
  }
49
65
  }
50
66
 
@@ -1,7 +1,3 @@
1
- /**
2
- * Gemini Streaming Service
3
- * Handles streaming content generation
4
- */
5
1
 
6
2
  import { geminiClientCoreService } from "./gemini-client-core.service";
7
3
  import type {
@@ -1,9 +1,6 @@
1
- /**
2
- * Gemini Structured Text Service
3
- * Handles structured JSON response generation with schema validation
4
- */
5
1
 
6
2
  import { geminiTextGenerationService } from "./gemini-text-generation.service";
3
+ import type { GenerationConfig } from "@google/generative-ai";
7
4
  import type {
8
5
  GeminiContent,
9
6
  GeminiGenerationConfig,
@@ -20,10 +17,16 @@ class GeminiStructuredTextService {
20
17
  schema: Record<string, unknown>,
21
18
  config?: Omit<GeminiGenerationConfig, "responseMimeType" | "responseSchema">,
22
19
  ): Promise<T> {
20
+ // Validate schema structure before passing to SDK
21
+ if (!schema || typeof schema !== "object" || Object.keys(schema).length === 0) {
22
+ throw new Error("Schema must be a non-empty object");
23
+ }
24
+
23
25
  const generationConfig: GeminiGenerationConfig = {
24
26
  ...config,
25
27
  responseMimeType: "application/json",
26
- responseSchema: schema as unknown as undefined,
28
+ // Pass schema directly - Google SDK will validate it
29
+ responseSchema: schema as GenerationConfig["responseSchema"],
27
30
  };
28
31
 
29
32
  const contents: GeminiContent[] = [
@@ -1,7 +1,3 @@
1
- /**
2
- * Gemini Text Generation Service
3
- * Handles text and multimodal content generation
4
- */
5
1
 
6
2
  import { geminiClientCoreService } from "./gemini-client-core.service";
7
3
  import { extractTextFromResponse } from "../utils/gemini-data-transformer.util";
@@ -12,8 +8,6 @@ import type {
12
8
  GeminiPart,
13
9
  } from "../../domain/entities";
14
10
 
15
- declare const __DEV__: boolean;
16
-
17
11
  class GeminiTextGenerationService {
18
12
  /**
19
13
  * Generate content (text, with optional images)
@@ -25,60 +19,37 @@ class GeminiTextGenerationService {
25
19
  ): Promise<GeminiResponse> {
26
20
  const genModel = geminiClientCoreService.getModel(model);
27
21
 
28
- if (typeof __DEV__ !== "undefined" && __DEV__) {
29
- // eslint-disable-next-line no-console
30
- console.log("[Gemini] Generate content:", { model });
31
- }
32
-
33
22
  const sdkContents = contents.map((content) => ({
34
23
  role: content.role || "user",
35
- parts: content.parts.map((part) => {
36
- return part;
37
- }),
24
+ parts: content.parts,
38
25
  }));
39
26
 
40
- try {
41
- const result = await genModel.generateContent({
42
- contents: sdkContents as Parameters<typeof genModel.generateContent>[0] extends { contents: infer C } ? C : never,
43
- generationConfig,
44
- });
27
+ const result = await genModel.generateContent({
28
+ contents: sdkContents as Parameters<typeof genModel.generateContent>[0] extends { contents: infer C } ? C : never,
29
+ generationConfig,
30
+ });
45
31
 
46
- const response = (result as { response: GeminiResponse }).response;
32
+ const response = (result as { response: GeminiResponse }).response;
47
33
 
48
- if (typeof __DEV__ !== "undefined" && __DEV__) {
49
- // eslint-disable-next-line no-console
50
- console.log("[Gemini] Content generated:", {
51
- candidatesCount: response.candidates?.length ?? 0,
52
- finishReason: response.candidates?.[0]?.finishReason,
53
- });
54
- }
34
+ return {
35
+ candidates: response.candidates?.map((candidate) => {
36
+ const transformedParts: GeminiPart[] = [];
37
+ for (const part of candidate.content.parts) {
38
+ if ("text" in part && typeof part.text === "string") {
39
+ transformedParts.push({ text: part.text });
40
+ }
41
+ // Ignore unsupported part types (inlineData, etc.)
42
+ }
55
43
 
56
- return {
57
- candidates: response.candidates?.map((candidate) => ({
44
+ return {
58
45
  content: {
59
- parts: candidate.content.parts
60
- .map((part): GeminiPart | null => {
61
- if ("text" in part && part.text !== undefined) {
62
- return { text: part.text };
63
- }
64
- return null;
65
- })
66
- .filter((p): p is GeminiPart => p !== null),
46
+ parts: transformedParts,
67
47
  role: (candidate.content.role || "model"),
68
48
  },
69
49
  finishReason: candidate.finishReason,
70
- })),
71
- };
72
- } catch (error) {
73
- if (typeof __DEV__ !== "undefined" && __DEV__) {
74
- // eslint-disable-next-line no-console
75
- console.error("[Gemini] Content generation failed:", {
76
- model,
77
- error: error instanceof Error ? error.message : String(error),
78
- });
79
- }
80
- throw error;
81
- }
50
+ };
51
+ }),
52
+ };
82
53
  }
83
54
 
84
55
  /**
@@ -103,5 +74,3 @@ class GeminiTextGenerationService {
103
74
  }
104
75
 
105
76
  export const geminiTextGenerationService = new GeminiTextGenerationService();
106
-
107
- export const geminiTextService = geminiTextGenerationService;