@umituz/react-native-ai-gemini-provider 2.1.13 → 3.0.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.
Files changed (28) hide show
  1. package/package.json +1 -1
  2. package/src/domain/entities/models.ts +0 -8
  3. package/src/index.ts +6 -7
  4. package/src/infrastructure/services/{base-gemini.service.ts → BaseService.ts} +3 -34
  5. package/src/infrastructure/services/{gemini-client-core.service.ts → GeminiClient.ts} +10 -25
  6. package/src/infrastructure/services/GeminiProvider.ts +38 -0
  7. package/src/infrastructure/services/{gemini-streaming.service.ts → Streaming.ts} +14 -35
  8. package/src/infrastructure/services/{gemini-structured-text.service.ts → StructuredText.ts} +7 -14
  9. package/src/infrastructure/services/{gemini-text-generation.service.ts → TextGeneration.ts} +8 -23
  10. package/src/infrastructure/services/index.ts +5 -21
  11. package/src/infrastructure/telemetry/index.ts +0 -1
  12. package/src/infrastructure/utils/async/index.ts +0 -2
  13. package/src/infrastructure/utils/content-mapper.util.ts +1 -1
  14. package/src/infrastructure/utils/error-mapper.util.ts +1 -9
  15. package/src/infrastructure/utils/json-parser.util.ts +1 -50
  16. package/src/infrastructure/utils/stream-processor.util.ts +3 -90
  17. package/src/presentation/hooks/index.ts +1 -11
  18. package/src/presentation/hooks/{use-gemini.ts → useGemini.ts} +5 -5
  19. package/src/providers/ProviderFactory.ts +2 -2
  20. package/src/infrastructure/interceptors/BaseInterceptor.ts +0 -78
  21. package/src/infrastructure/interceptors/RequestInterceptors.ts +0 -30
  22. package/src/infrastructure/interceptors/ResponseInterceptors.ts +0 -35
  23. package/src/infrastructure/interceptors/index.ts +0 -12
  24. package/src/infrastructure/services/gemini-provider.ts +0 -62
  25. package/src/infrastructure/utils/index.ts +0 -47
  26. package/src/infrastructure/utils/validation-composer.util.ts +0 -160
  27. package/src/infrastructure/utils/validation.util.ts +0 -73
  28. /package/src/presentation/hooks/{use-operation-manager.ts → useOperationManager.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-gemini-provider",
3
- "version": "2.1.13",
3
+ "version": "3.0.1",
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",
@@ -16,11 +16,3 @@ export const DEFAULT_MODELS = {
16
16
  /** Default model for text generation */
17
17
  TEXT: GEMINI_MODELS.TEXT.FLASH_LITE,
18
18
  } as const;
19
-
20
- /**
21
- * Pricing information for Gemini models
22
- * Prices are per 1M tokens (USD)
23
- */
24
- export const MODEL_PRICING = {
25
- [GEMINI_MODELS.TEXT.FLASH_LITE]: { input: 0.10, output: 0.40, freePerDay: 1000 },
26
- } as const;
package/src/index.ts CHANGED
@@ -27,13 +27,12 @@ export {
27
27
  DEFAULT_MODELS
28
28
  } from "./domain/entities";
29
29
 
30
- // Main Service
31
- export {
32
- geminiProviderService,
33
- GeminiProvider,
34
- } from "./infrastructure/services";
35
-
36
- export type { GeminiProviderConfig } from "./infrastructure/services";
30
+ // Services
31
+ export { geminiClient } from "./infrastructure/services/GeminiClient";
32
+ export { textGeneration } from "./infrastructure/services/TextGeneration";
33
+ export { structuredText } from "./infrastructure/services/StructuredText";
34
+ export { streaming } from "./infrastructure/services/Streaming";
35
+ export { geminiProvider, GeminiProvider } from "./infrastructure/services/GeminiProvider";
37
36
 
38
37
  // React Hook
39
38
  export { useGemini } from "./presentation/hooks";
@@ -1,17 +1,9 @@
1
- /**
2
- * Base Gemini Service
3
- * Common functionality for all Gemini services to eliminate code duplication
4
- */
5
-
6
- import { geminiClientCoreService } from "./gemini-client-core.service";
1
+ import { geminiClient } from "./GeminiClient";
7
2
  import { toSdkContent } from "../utils/content-mapper.util";
8
3
  import { createGeminiError } from "../utils/error-mapper.util";
9
4
  import type { GeminiContent, GeminiGenerationConfig } from "../../domain/entities";
10
5
  import type { GenerativeModel } from "@google/generative-ai";
11
6
 
12
- /**
13
- * Base request options structure
14
- */
15
7
  export interface BaseRequestOptions {
16
8
  model: string;
17
9
  contents: GeminiContent[];
@@ -19,64 +11,41 @@ export interface BaseRequestOptions {
19
11
  signal?: AbortSignal;
20
12
  }
21
13
 
22
- /**
23
- * Abstract base service with common patterns
24
- */
25
14
  export abstract class BaseGeminiService {
26
- /**
27
- * Validate and prepare request
28
- * Eliminates duplicate validation and abort checks
29
- */
30
15
  protected validateAndPrepare(options: BaseRequestOptions): {
31
16
  genModel: GenerativeModel;
32
17
  sdkContents: Array<{ role: string; parts: Array<{ text: string }> }>;
33
18
  } {
34
- // Validate contents
35
19
  if (!options.contents || options.contents.length === 0) {
36
20
  throw new Error("Contents array cannot be empty");
37
21
  }
38
22
 
39
- // Check for early abort
40
23
  if (options.signal?.aborted) {
41
24
  throw new Error("Request was aborted");
42
25
  }
43
26
 
44
- const genModel = geminiClientCoreService.getModel(options.model);
27
+ const genModel = geminiClient.getModel(options.model);
45
28
  const sdkContents = toSdkContent(options.contents);
46
29
 
47
30
  return { genModel, sdkContents };
48
31
  }
49
32
 
50
- /**
51
- * Handle errors uniformly across all services
52
- * Eliminates duplicate error handling logic
53
- */
54
33
  protected handleError(error: unknown, abortMessage: string): never {
55
- // Re-throw as GeminiError if it's already an API error
56
34
  if (error instanceof Error && error.name === "GeminiError") {
57
35
  throw error;
58
36
  }
59
37
 
60
- // Check for abort error
61
38
  if (error instanceof Error && error.name === "AbortError") {
62
39
  throw new Error(abortMessage);
63
40
  }
64
41
 
65
- // Wrap other errors
66
42
  throw createGeminiError(error);
67
43
  }
68
44
 
69
- /**
70
- * Create typed request options for SDK
71
- * Type-safe request creation
72
- */
73
45
  protected createRequestOptions(
74
46
  sdkContents: Array<{ role: string; parts: Array<{ text: string }> }>,
75
47
  generationConfig?: GeminiGenerationConfig
76
48
  ) {
77
- return {
78
- contents: sdkContents,
79
- generationConfig,
80
- };
49
+ return { contents: sdkContents, generationConfig };
81
50
  }
82
51
  }
@@ -1,29 +1,22 @@
1
1
  import { GoogleGenerativeAI, type GenerativeModel } from "@google/generative-ai";
2
2
  import { DEFAULT_MODELS } from "../../domain/entities";
3
3
  import type { GeminiConfig } from "../../domain/entities";
4
- import { validateModelName, validateApiKey } from "../utils/validation.util";
5
4
 
6
5
  const DEFAULT_CONFIG: Partial<GeminiConfig> = {
7
6
  textModel: DEFAULT_MODELS.TEXT,
8
7
  };
9
8
 
10
- class GeminiClientCoreService {
9
+ class GeminiClient {
11
10
  private client: GoogleGenerativeAI | null = null;
12
11
  private config: GeminiConfig | null = null;
13
12
  private initialized = false;
14
13
 
15
- /**
16
- * Initialize the Gemini client with configuration
17
- *
18
- * @throws {Error} If already initialized or API key is invalid
19
- */
20
14
  initialize(config: GeminiConfig): void {
21
- if (this.initialized) {
22
- throw new Error("Gemini client already initialized. Call reset() before re-initializing with new config.");
23
- }
15
+ if (this.initialized) return;
24
16
 
25
- // Validate API key
26
- validateApiKey(config.apiKey);
17
+ if (!config.apiKey || config.apiKey.trim().length < 10) {
18
+ throw new Error("API key is required and must be at least 10 characters");
19
+ }
27
20
 
28
21
  this.client = new GoogleGenerativeAI(config.apiKey);
29
22
  this.config = { ...DEFAULT_CONFIG, ...config };
@@ -42,24 +35,16 @@ class GeminiClientCoreService {
42
35
  return this.client;
43
36
  }
44
37
 
45
- validateInitialization(): void {
38
+ getModel(modelName?: string): GenerativeModel {
46
39
  if (!this.client || !this.initialized) {
47
40
  throw new Error("Gemini client not initialized. Call initialize() first.");
48
41
  }
49
- }
50
-
51
-
52
- getModel(modelName?: string): GenerativeModel {
53
- this.validateInitialization();
54
-
55
- if (!this.client) {
56
- throw new Error("Gemini client not available");
57
- }
58
42
 
59
43
  const effectiveModel = modelName || this.config?.textModel || DEFAULT_MODELS.TEXT;
60
44
 
61
- // Validate model name format
62
- validateModelName(effectiveModel);
45
+ if (!effectiveModel.startsWith("gemini-")) {
46
+ throw new Error('Model name must start with "gemini-"');
47
+ }
63
48
 
64
49
  return this.client.getGenerativeModel({ model: effectiveModel });
65
50
  }
@@ -71,4 +56,4 @@ class GeminiClientCoreService {
71
56
  }
72
57
  }
73
58
 
74
- export const geminiClientCoreService = new GeminiClientCoreService();
59
+ export const geminiClient = new GeminiClient();
@@ -0,0 +1,38 @@
1
+ import type { GeminiConfig } from "../../domain/entities";
2
+ import { geminiClient } from "./GeminiClient";
3
+ import { structuredText } from "./StructuredText";
4
+
5
+ export class GeminiProvider {
6
+ readonly providerId = "gemini";
7
+ readonly providerName = "Google Gemini";
8
+
9
+ initialize(config: GeminiConfig): void {
10
+ geminiClient.initialize(config);
11
+ }
12
+
13
+ isInitialized(): boolean {
14
+ return geminiClient.isInitialized();
15
+ }
16
+
17
+ reset(): void {
18
+ geminiClient.reset();
19
+ }
20
+
21
+ async generateStructuredText<T>(
22
+ prompt: string,
23
+ schema: Record<string, unknown>,
24
+ model: string,
25
+ ): Promise<T> {
26
+ if (!prompt || prompt.trim().length < 3) {
27
+ throw new Error("Prompt must be at least 3 characters");
28
+ }
29
+
30
+ if (!this.isInitialized()) {
31
+ throw new Error("Provider not initialized. Call initialize() first.");
32
+ }
33
+
34
+ return structuredText.generateStructuredText<T>(model, prompt, schema);
35
+ }
36
+ }
37
+
38
+ export const geminiProvider = new GeminiProvider();
@@ -1,4 +1,4 @@
1
- import { BaseGeminiService } from "./base-gemini.service";
1
+ import { BaseGeminiService } from "./BaseService";
2
2
  import { telemetryHooks } from "../telemetry";
3
3
  import { processStream } from "../utils/stream-processor.util";
4
4
  import type {
@@ -6,22 +6,7 @@ import type {
6
6
  GeminiGenerationConfig,
7
7
  } from "../../domain/entities";
8
8
 
9
- class GeminiStreamingService extends BaseGeminiService {
10
- /**
11
- * Stream content generation
12
- *
13
- * @throws {GeminiError} For API-specific errors
14
- * @throws {Error} For validation or network errors
15
- *
16
- * @example
17
- * ```ts
18
- * const fullText = await streamContent(
19
- * "gemini-2.5-flash-lite",
20
- * [{ parts: [{ text: "Hello" }], role: "user" }],
21
- * (chunk) => console.log(chunk)
22
- * );
23
- * ```
24
- */
9
+ class StreamingService extends BaseGeminiService {
25
10
  async streamContent(
26
11
  model: string,
27
12
  contents: GeminiContent[],
@@ -29,7 +14,6 @@ class GeminiStreamingService extends BaseGeminiService {
29
14
  generationConfig?: GeminiGenerationConfig,
30
15
  signal?: AbortSignal,
31
16
  ): Promise<string> {
32
- // Validate callback
33
17
  if (typeof onChunk !== "function") {
34
18
  throw new Error("onChunk must be a function");
35
19
  }
@@ -51,27 +35,22 @@ class GeminiStreamingService extends BaseGeminiService {
51
35
  return await processStream(
52
36
  result.stream,
53
37
  onChunk,
54
- (error, context) => this.logStreamError(model, error, context)
38
+ (error, context) => {
39
+ try {
40
+ telemetryHooks.logError(
41
+ model,
42
+ error instanceof Error ? error : new Error(String(error)),
43
+ context
44
+ );
45
+ } catch {
46
+ // Silently ignore telemetry errors
47
+ }
48
+ }
55
49
  );
56
50
  } catch (error) {
57
51
  return this.handleError(error, "Stream generation was aborted");
58
52
  }
59
53
  }
60
-
61
- /**
62
- * Log stream errors via telemetry
63
- */
64
- private logStreamError(model: string, error: unknown, context?: string): void {
65
- try {
66
- telemetryHooks.logError(
67
- model,
68
- error instanceof Error ? error : new Error(String(error)),
69
- context
70
- );
71
- } catch {
72
- // Silently ignore telemetry errors
73
- }
74
- }
75
54
  }
76
55
 
77
- export const geminiStreamingService = new GeminiStreamingService();
56
+ export const streaming = new StreamingService();
@@ -1,8 +1,6 @@
1
-
2
- import { geminiTextGenerationService } from "./gemini-text-generation.service";
1
+ import { textGeneration } from "./TextGeneration";
3
2
  import { parseJsonResponse } from "../utils/json-parser.util";
4
3
  import { extractTextFromParts } from "../utils/content-mapper.util";
5
- import { validateSchema } from "../utils/validation.util";
6
4
  import type { GenerationConfig } from "@google/generative-ai";
7
5
  import type {
8
6
  GeminiContent,
@@ -10,11 +8,7 @@ import type {
10
8
  GeminiResponse,
11
9
  } from "../../domain/entities";
12
10
 
13
-
14
- class GeminiStructuredTextService {
15
- /**
16
- * Generate structured JSON response with schema
17
- */
11
+ class StructuredTextService {
18
12
  async generateStructuredText<T>(
19
13
  model: string,
20
14
  prompt: string,
@@ -22,7 +16,9 @@ class GeminiStructuredTextService {
22
16
  config?: Omit<GeminiGenerationConfig, "responseMimeType" | "responseSchema">,
23
17
  signal?: AbortSignal,
24
18
  ): Promise<T> {
25
- validateSchema(schema);
19
+ if (!schema || typeof schema !== "object" || Object.keys(schema).length === 0) {
20
+ throw new Error("Schema must be a non-empty object");
21
+ }
26
22
 
27
23
  const generationConfig: GeminiGenerationConfig = {
28
24
  ...config,
@@ -34,7 +30,7 @@ class GeminiStructuredTextService {
34
30
  { parts: [{ text: prompt }], role: "user" },
35
31
  ];
36
32
 
37
- const response = await geminiTextGenerationService.generateContent(
33
+ const response = await textGeneration.generateContent(
38
34
  model,
39
35
  contents,
40
36
  generationConfig,
@@ -44,9 +40,6 @@ class GeminiStructuredTextService {
44
40
  return this.parseJSONResponse<T>(response);
45
41
  }
46
42
 
47
- /**
48
- * Parse JSON response from Gemini
49
- */
50
43
  private parseJSONResponse<T>(response: GeminiResponse): T {
51
44
  const candidates = response.candidates;
52
45
 
@@ -64,4 +57,4 @@ class GeminiStructuredTextService {
64
57
  }
65
58
  }
66
59
 
67
- export const geminiStructuredTextService = new GeminiStructuredTextService();
60
+ export const structuredText = new StructuredTextService();
@@ -1,20 +1,13 @@
1
- import { BaseGeminiService } from "./base-gemini.service";
1
+ import { BaseGeminiService } from "./BaseService";
2
2
  import { extractTextFromResponse } from "../utils/gemini-data-transformer.util";
3
3
  import { transformResponse, createTextContent } from "../utils/content-mapper.util";
4
- import { validatePrompt } from "../utils/validation.util";
5
4
  import type {
6
5
  GeminiContent,
7
6
  GeminiGenerationConfig,
8
7
  GeminiResponse,
9
8
  } from "../../domain/entities";
10
9
 
11
- class GeminiTextGenerationService extends BaseGeminiService {
12
- /**
13
- * Generate content (text, with optional images)
14
- *
15
- * @throws {GeminiError} For API-specific errors
16
- * @throws {Error} For validation or network errors
17
- */
10
+ class TextGenerationService extends BaseGeminiService {
18
11
  async generateContent(
19
12
  model: string,
20
13
  contents: GeminiContent[],
@@ -35,38 +28,30 @@ class GeminiTextGenerationService extends BaseGeminiService {
35
28
  ? await genModel.generateContent(requestOptions, { signal })
36
29
  : await genModel.generateContent(requestOptions);
37
30
 
38
- const response = result.response;
39
-
40
- if (!response) {
31
+ if (!result.response) {
41
32
  throw new Error("No response received from Gemini API");
42
33
  }
43
34
 
44
- return transformResponse(response);
35
+ return transformResponse(result.response);
45
36
  } catch (error) {
46
37
  return this.handleError(error, "Request was aborted");
47
38
  }
48
39
  }
49
40
 
50
- /**
51
- * Generate text from prompt
52
- *
53
- * @throws {GeminiError} For API-specific errors
54
- * @throws {Error} For validation or network errors
55
- */
56
41
  async generateText(
57
42
  model: string,
58
43
  prompt: string,
59
44
  config?: GeminiGenerationConfig,
60
45
  signal?: AbortSignal,
61
46
  ): Promise<string> {
62
- // Validate prompt
63
- validatePrompt(prompt);
47
+ if (!prompt || prompt.trim().length < 3) {
48
+ throw new Error("Prompt must be at least 3 characters");
49
+ }
64
50
 
65
51
  const contents: GeminiContent[] = [createTextContent(prompt, "user")];
66
-
67
52
  const response = await this.generateContent(model, contents, config, signal);
68
53
  return extractTextFromResponse(response);
69
54
  }
70
55
  }
71
56
 
72
- export const geminiTextGenerationService = new GeminiTextGenerationService();
57
+ export const textGeneration = new TextGenerationService();
@@ -1,21 +1,5 @@
1
- /**
2
- * Infrastructure Services - Internal Use Only
3
- */
4
-
5
- // Internal base classes
6
- export { BaseGeminiService } from "./base-gemini.service";
7
- export type { BaseRequestOptions } from "./base-gemini.service";
8
-
9
- // Internal services
10
- export { geminiClientCoreService } from "./gemini-client-core.service";
11
- export { geminiTextGenerationService } from "./gemini-text-generation.service";
12
- export { geminiStructuredTextService } from "./gemini-structured-text.service";
13
- export { geminiStreamingService } from "./gemini-streaming.service";
14
-
15
- // Main Provider - Public API
16
- export {
17
- geminiProviderService,
18
- GeminiProvider,
19
- } from "./gemini-provider";
20
- export type { GeminiProviderConfig } from "./gemini-provider";
21
-
1
+ export { geminiClient } from "./GeminiClient";
2
+ export { textGeneration } from "./TextGeneration";
3
+ export { structuredText } from "./StructuredText";
4
+ export { streaming } from "./Streaming";
5
+ export { geminiProvider, GeminiProvider } from "./GeminiProvider";
@@ -3,4 +3,3 @@
3
3
  */
4
4
 
5
5
  export { telemetryHooks } from "./TelemetryHooks";
6
- export type { TelemetryEvent, TelemetryListener } from "./TelemetryHooks";
@@ -4,7 +4,5 @@
4
4
 
5
5
  export {
6
6
  executeWithState,
7
- type AsyncStateCallbacks,
8
7
  type AsyncStateSetters,
9
- type AsyncStateConfig,
10
8
  } from "./execute-state.util";
@@ -76,7 +76,7 @@ export function createTextContent(
76
76
  /**
77
77
  * Transform SDK candidate to domain format
78
78
  */
79
- export function transformCandidate(
79
+ function transformCandidate(
80
80
  candidate: {
81
81
  content: { parts: Array<{ text?: string }>; role?: string };
82
82
  finishReason?: string;
@@ -87,7 +87,7 @@ function matchesPattern(message: string, patterns: string[]): boolean {
87
87
  });
88
88
  }
89
89
 
90
- export function mapGeminiError(error: unknown): GeminiErrorInfo {
90
+ function mapGeminiError(error: unknown): GeminiErrorInfo {
91
91
  const message = error instanceof Error ? error.message : String(error);
92
92
  const statusCode = getStatusCode(error);
93
93
 
@@ -114,14 +114,6 @@ export function mapGeminiError(error: unknown): GeminiErrorInfo {
114
114
  };
115
115
  }
116
116
 
117
- export function isGeminiErrorRetryable(error: unknown): boolean {
118
- return mapGeminiError(error).retryable;
119
- }
120
-
121
- export function categorizeGeminiError(error: unknown): GeminiErrorType {
122
- return mapGeminiError(error).type;
123
- }
124
-
125
117
  export function createGeminiError(error: unknown): GeminiError {
126
118
  const errorInfo = mapGeminiError(error);
127
119
  return GeminiError.fromError(error, errorInfo);
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Clean JSON text by removing markdown code blocks and extra whitespace
8
8
  */
9
- export function cleanJsonText(text: string): string {
9
+ function cleanJsonText(text: string): string {
10
10
  if (!text || typeof text !== "string") {
11
11
  return "";
12
12
  }
@@ -39,52 +39,3 @@ export function parseJsonResponse<T>(text: string): T {
39
39
  }
40
40
  }
41
41
 
42
- /**
43
- * Safely parse JSON with optional fallback value
44
- */
45
- export function safeParseJson<T>(
46
- text: string,
47
- fallback: T
48
- ): T {
49
- try {
50
- return parseJsonResponse<T>(text);
51
- } catch {
52
- return fallback;
53
- }
54
- }
55
-
56
- /**
57
- * Extract and parse JSON from a larger text response
58
- * Looks for JSON objects within markdown code blocks or standalone
59
- */
60
- export function extractJsonFromText<T>(text: string): T | null {
61
- // Try to find JSON in code blocks first
62
- const codeBlockMatch = text.match(/```json\s*([\s\S]*?)\s*```/);
63
- if (codeBlockMatch) {
64
- try {
65
- return JSON.parse(codeBlockMatch[1].trim()) as T;
66
- } catch {
67
- // Continue to other methods
68
- }
69
- }
70
-
71
- // Try to find JSON object boundaries
72
- const firstBrace = text.indexOf("{");
73
- const lastBrace = text.lastIndexOf("}");
74
-
75
- if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
76
- try {
77
- const jsonStr = text.substring(firstBrace, lastBrace + 1);
78
- return JSON.parse(jsonStr) as T;
79
- } catch {
80
- // Continue to fallback
81
- }
82
- }
83
-
84
- // Try parsing the whole text as JSON
85
- try {
86
- return parseJsonResponse<T>(text);
87
- } catch {
88
- return null;
89
- }
90
- }
@@ -3,12 +3,12 @@
3
3
  * Reusable stream handling logic
4
4
  */
5
5
 
6
- export interface StreamChunk {
6
+ interface StreamChunk {
7
7
  text: () => string;
8
8
  }
9
9
 
10
- export type ChunkCallback = (text: string) => void;
11
- export type ErrorLogger = (error: unknown, context?: string) => void;
10
+ type ChunkCallback = (text: string) => void;
11
+ type ErrorLogger = (error: unknown, context?: string) => void;
12
12
 
13
13
  /**
14
14
  * Process async stream with chunk callback
@@ -67,90 +67,3 @@ function logError(
67
67
  }
68
68
  }
69
69
 
70
- /**
71
- * Create a buffered stream processor
72
- * Accumulates chunks until a condition is met
73
- */
74
- export class BufferedStreamProcessor {
75
- private buffer = "";
76
- private fullText = "";
77
-
78
- constructor(
79
- private onFlush: (text: string) => void,
80
- private flushCondition: (buffer: string) => boolean = () => false
81
- ) {}
82
-
83
- /**
84
- * Process a single chunk
85
- */
86
- processChunk(chunk: StreamChunk): void {
87
- try {
88
- const chunkText = chunk.text();
89
- if (!chunkText) return;
90
-
91
- this.buffer += chunkText;
92
- this.fullText += chunkText;
93
-
94
- if (this.flushCondition(this.buffer)) {
95
- this.flush();
96
- }
97
- } catch (error) {
98
- // Ignore chunk errors
99
- }
100
- }
101
-
102
- /**
103
- * Flush buffer to callback
104
- */
105
- flush(): void {
106
- if (this.buffer) {
107
- try {
108
- this.onFlush(this.buffer);
109
- this.buffer = "";
110
- } catch {
111
- // Ignore callback errors
112
- }
113
- }
114
- }
115
-
116
- /**
117
- * Get accumulated full text
118
- */
119
- getFullText(): string {
120
- return this.fullText;
121
- }
122
-
123
- /**
124
- * Process entire stream
125
- */
126
- async processStream(stream: AsyncIterable<StreamChunk>): Promise<string> {
127
- for await (const chunk of stream) {
128
- this.processChunk(chunk);
129
- }
130
-
131
- // Flush remaining buffer
132
- this.flush();
133
-
134
- return this.fullText;
135
- }
136
- }
137
-
138
- /**
139
- * Common flush conditions
140
- */
141
- export const flushConditions = {
142
- /**
143
- * Flush on newline
144
- */
145
- onNewline: (buffer: string): boolean => buffer.includes("\n"),
146
-
147
- /**
148
- * Flush when buffer reaches size
149
- */
150
- onSize: (size: number) => (buffer: string): boolean => buffer.length >= size,
151
-
152
- /**
153
- * Flush on pattern match
154
- */
155
- onPattern: (pattern: RegExp) => (buffer: string): boolean => pattern.test(buffer),
156
- };
@@ -1,11 +1 @@
1
- /**
2
- * React Hooks - Public API
3
- */
4
-
5
- // Internal - not exported from main index
6
- export { useOperationManager } from "./use-operation-manager";
7
- export type { OperationManager } from "./use-operation-manager";
8
-
9
- // Public API
10
- export { useGemini } from "./use-gemini";
11
- export type { UseGeminiOptions, UseGeminiReturn } from "./use-gemini";
1
+ export { useGemini, type UseGeminiOptions, type UseGeminiReturn } from "./useGemini";
@@ -1,10 +1,10 @@
1
1
  import { useState, useCallback, useMemo } from "react";
2
2
  import type { GeminiGenerationConfig } from "../../domain/entities";
3
3
  import { DEFAULT_MODELS } from "../../domain/entities";
4
- import { geminiTextGenerationService, geminiStructuredTextService } from "../../infrastructure/services";
4
+ import { textGeneration, structuredText } from "../../infrastructure/services";
5
5
  import { executeWithState, type AsyncStateSetters } from "../../infrastructure/utils/async";
6
6
  import { parseJsonResponse } from "../../infrastructure/utils/json-parser.util";
7
- import { useOperationManager } from "./use-operation-manager";
7
+ import { useOperationManager } from "./useOperationManager";
8
8
 
9
9
  export interface UseGeminiOptions {
10
10
  model?: string;
@@ -55,7 +55,7 @@ export function useGemini(options: UseGeminiOptions = {}): UseGeminiReturn {
55
55
  setters,
56
56
  callbacks,
57
57
  async () => {
58
- return geminiTextGenerationService.generateText(
58
+ return textGeneration.generateText(
59
59
  model,
60
60
  prompt,
61
61
  options.generationConfig,
@@ -95,7 +95,7 @@ export function useGemini(options: UseGeminiOptions = {}): UseGeminiReturn {
95
95
  jsonCallbacks,
96
96
  async () => {
97
97
  if (schema) {
98
- return geminiStructuredTextService.generateStructuredText<T>(
98
+ return structuredText.generateStructuredText<T>(
99
99
  model,
100
100
  prompt,
101
101
  schema,
@@ -104,7 +104,7 @@ export function useGemini(options: UseGeminiOptions = {}): UseGeminiReturn {
104
104
  );
105
105
  }
106
106
 
107
- const text = await geminiTextGenerationService.generateText(
107
+ const text = await textGeneration.generateText(
108
108
  model,
109
109
  prompt,
110
110
  { ...options.generationConfig, responseMimeType: "application/json" },
@@ -1,4 +1,4 @@
1
- import { geminiClientCoreService } from "../infrastructure/services/gemini-client-core.service";
1
+ import { geminiClient } from "../infrastructure/services/GeminiClient";
2
2
  import { ConfigBuilder, type ProviderConfig } from "./ConfigBuilder";
3
3
 
4
4
  // Re-export for public API
@@ -40,7 +40,7 @@ class ProviderFactory {
40
40
 
41
41
  // Initialize Gemini client
42
42
  const geminiConfig = this.builder.toGeminiConfig();
43
- geminiClientCoreService.initialize(geminiConfig);
43
+ geminiClient.initialize(geminiConfig);
44
44
  }
45
45
 
46
46
  /**
@@ -1,78 +0,0 @@
1
- /**
2
- * Base Interceptor Class
3
- * Eliminates code duplication between Request and Response interceptors
4
- */
5
-
6
- import { telemetryHooks } from "../telemetry";
7
-
8
- export type InterceptorErrorStrategy = "fail" | "skip" | "log";
9
-
10
- export interface BaseContext {
11
- model: string;
12
- feature?: string;
13
- timestamp: number;
14
- }
15
-
16
- export abstract class BaseInterceptor<TContext extends BaseContext> {
17
- protected interceptors: Array<(context: TContext) => TContext | Promise<TContext>> = [];
18
- protected errorStrategy: InterceptorErrorStrategy = "fail";
19
-
20
- /**
21
- * Register an interceptor
22
- */
23
- use(interceptor: (context: TContext) => TContext | Promise<TContext>): () => void {
24
- this.interceptors.push(interceptor);
25
-
26
- // Return unsubscribe function
27
- return () => {
28
- const index = this.interceptors.indexOf(interceptor);
29
- if (index > -1) {
30
- this.interceptors.splice(index, 1);
31
- }
32
- };
33
- }
34
-
35
- /**
36
- * Set error handling strategy
37
- */
38
- setErrorStrategy(strategy: InterceptorErrorStrategy): void {
39
- this.errorStrategy = strategy;
40
- }
41
-
42
- /**
43
- * Clear all interceptors
44
- */
45
- clear(): void {
46
- this.interceptors = [];
47
- }
48
-
49
- /**
50
- * Get interceptor count
51
- */
52
- count(): number {
53
- return this.interceptors.length;
54
- }
55
-
56
- /**
57
- * Handle interceptor error based on strategy
58
- */
59
- protected handleError(context: TContext, error: unknown): void {
60
- telemetryHooks.logError(
61
- context.model,
62
- error instanceof Error ? error : new Error(String(error)),
63
- context.feature
64
- );
65
-
66
- if (this.errorStrategy === "fail") {
67
- throw new Error(
68
- `Interceptor failed: ${error instanceof Error ? error.message : String(error)}`
69
- );
70
- }
71
- // For "skip" and "log", we just continue (error already logged)
72
- }
73
-
74
- /**
75
- * Apply interceptors - to be implemented by subclasses
76
- */
77
- abstract apply(context: TContext): Promise<TContext>;
78
- }
@@ -1,30 +0,0 @@
1
- import { BaseInterceptor, type BaseContext } from "./BaseInterceptor";
2
-
3
- export interface RequestContext extends BaseContext {
4
- payload: Record<string, unknown>;
5
- }
6
-
7
- export type RequestInterceptor = (context: RequestContext) => RequestContext | Promise<RequestContext>;
8
-
9
- class RequestInterceptors extends BaseInterceptor<RequestContext> {
10
- /**
11
- * Apply all interceptors to a request context
12
- * Interceptors are called in order (first registered = first called)
13
- */
14
- async apply(context: RequestContext): Promise<RequestContext> {
15
- let result = context;
16
-
17
- for (const interceptor of this.interceptors) {
18
- try {
19
- result = await interceptor(result);
20
- } catch (error) {
21
- this.handleError(context, error);
22
- // If we get here, strategy was "skip" or "log" - continue with previous result
23
- }
24
- }
25
-
26
- return result;
27
- }
28
- }
29
-
30
- export const requestInterceptors = new RequestInterceptors();
@@ -1,35 +0,0 @@
1
- import { BaseInterceptor, type BaseContext } from "./BaseInterceptor";
2
-
3
- export interface ResponseContext<T = unknown> extends BaseContext {
4
- data: T;
5
- duration: number;
6
- }
7
-
8
- export type ResponseInterceptor<T = unknown> = (
9
- context: ResponseContext<T>,
10
- ) => ResponseContext<T> | Promise<ResponseContext<T>>;
11
-
12
- class ResponseInterceptors extends BaseInterceptor<ResponseContext<unknown>> {
13
- /**
14
- * Apply all interceptors to a response context
15
- * Interceptors are called in reverse order (last registered = first called)
16
- */
17
- async apply<T>(context: ResponseContext<T>): Promise<ResponseContext<T>> {
18
- let result: ResponseContext<unknown> = context;
19
-
20
- // Apply in reverse order (last added = first processed)
21
- for (let i = this.interceptors.length - 1; i >= 0; i--) {
22
- const interceptor = this.interceptors[i];
23
- try {
24
- result = await interceptor(result);
25
- } catch (error) {
26
- this.handleError(context, error);
27
- // If we get here, strategy was "skip" or "log" - continue with previous result
28
- }
29
- }
30
-
31
- return result as ResponseContext<T>;
32
- }
33
- }
34
-
35
- export const responseInterceptors = new ResponseInterceptors();
@@ -1,12 +0,0 @@
1
- /**
2
- * Interceptors Module - Internal Use Only
3
- */
4
-
5
- export { BaseInterceptor } from "./BaseInterceptor";
6
- export type { BaseContext, InterceptorErrorStrategy } from "./BaseInterceptor";
7
-
8
- export { requestInterceptors } from "./RequestInterceptors";
9
- export type { RequestContext, RequestInterceptor } from "./RequestInterceptors";
10
-
11
- export { responseInterceptors } from "./ResponseInterceptors";
12
- export type { ResponseContext, ResponseInterceptor } from "./ResponseInterceptors";
@@ -1,62 +0,0 @@
1
-
2
- import type { GeminiConfig } from "../../domain/entities";
3
- import { geminiClientCoreService } from "./gemini-client-core.service";
4
- import { geminiStructuredTextService } from "./gemini-structured-text.service";
5
- import { validatePrompt } from "../utils/validation.util";
6
-
7
- export type GeminiProviderConfig = GeminiConfig;
8
-
9
- export class GeminiProvider {
10
- readonly providerId = "gemini";
11
- readonly providerName = "Google Gemini";
12
-
13
- /**
14
- * Initialize the Gemini provider
15
- *
16
- * @throws {Error} If already initialized or configuration is invalid
17
- */
18
- initialize(config: GeminiProviderConfig): void {
19
- if (geminiClientCoreService.isInitialized()) {
20
- throw new Error("Provider already initialized. Call reset() before re-initializing with new config.");
21
- }
22
- geminiClientCoreService.initialize(config);
23
- }
24
-
25
- /**
26
- * Check if provider is initialized
27
- */
28
- isInitialized(): boolean {
29
- return geminiClientCoreService.isInitialized();
30
- }
31
-
32
- /**
33
- * Reset the provider to uninitialized state
34
- */
35
- reset(): void {
36
- geminiClientCoreService.reset();
37
- }
38
-
39
- /**
40
- * Generate structured JSON response
41
- *
42
- * @throws {GeminiError} For API-specific errors
43
- * @throws {Error} For validation or network errors
44
- */
45
- async generateStructuredText<T>(
46
- prompt: string,
47
- schema: Record<string, unknown>,
48
- model: string,
49
- ): Promise<T> {
50
- // Validate inputs
51
- validatePrompt(prompt);
52
-
53
- // Check if initialized
54
- if (!this.isInitialized()) {
55
- throw new Error("Provider not initialized. Call initialize() first.");
56
- }
57
-
58
- return geminiStructuredTextService.generateStructuredText<T>(model, prompt, schema);
59
- }
60
- }
61
-
62
- export const geminiProviderService = new GeminiProvider();
@@ -1,47 +0,0 @@
1
- /**
2
- * Utility Functions - Internal Use Only
3
- * These are internal implementation details and should not be used directly by consumers
4
- */
5
-
6
- // Error handling (internal)
7
- export {
8
- mapGeminiError,
9
- isGeminiErrorRetryable,
10
- categorizeGeminiError,
11
- createGeminiError
12
- } from "./error-mapper.util";
13
-
14
- // Data transformation (internal)
15
- export { extractTextFromResponse } from "./gemini-data-transformer.util";
16
- export {
17
- cleanJsonText,
18
- parseJsonResponse,
19
- safeParseJson,
20
- extractJsonFromText
21
- } from "./json-parser.util";
22
- export {
23
- toSdkContent,
24
- createTextContent,
25
- transformCandidate,
26
- transformResponse,
27
- extractTextFromParts
28
- } from "./content-mapper.util";
29
-
30
- // Validation (internal)
31
- export {
32
- validateModelName,
33
- validateApiKey,
34
- validateSchema,
35
- validatePrompt,
36
- validateTimeout,
37
- isValidObject,
38
- validateRequiredFields
39
- } from "./validation.util";
40
-
41
- // Async state management (internal)
42
- export {
43
- executeWithState,
44
- type AsyncStateCallbacks,
45
- type AsyncStateSetters,
46
- type AsyncStateConfig
47
- } from "./async";
@@ -1,160 +0,0 @@
1
- /**
2
- * Validation Composers
3
- * Composable validation rules for clean, reusable validation
4
- */
5
-
6
- export type ValidationRule<T = unknown> = (value: T) => string | null;
7
-
8
- /**
9
- * Compose multiple validation rules into one
10
- */
11
- export function compose<T>(...rules: ValidationRule<T>[]): ValidationRule<T> {
12
- return (value: T): string | null => {
13
- for (const rule of rules) {
14
- const error = rule(value);
15
- if (error) return error;
16
- }
17
- return null;
18
- };
19
- }
20
-
21
- /**
22
- * Validate that value is not empty
23
- */
24
- export function required(fieldName: string = "Field"): ValidationRule<string> {
25
- return (value: string): string | null => {
26
- if (!value || typeof value !== "string" || value.trim().length === 0) {
27
- return `${fieldName} is required`;
28
- }
29
- return null;
30
- };
31
- }
32
-
33
- /**
34
- * Validate minimum length
35
- */
36
- export function minLength(min: number, fieldName: string = "Field"): ValidationRule<string> {
37
- return (value: string): string | null => {
38
- if (value.trim().length < min) {
39
- return `${fieldName} must be at least ${min} characters`;
40
- }
41
- return null;
42
- };
43
- }
44
-
45
- /**
46
- * Validate maximum length
47
- */
48
- export function maxLength(max: number, fieldName: string = "Field"): ValidationRule<string> {
49
- return (value: string): string | null => {
50
- if (value.length > max) {
51
- return `${fieldName} must be at most ${max} characters`;
52
- }
53
- return null;
54
- };
55
- }
56
-
57
- /**
58
- * Validate string starts with prefix
59
- */
60
- export function startsWith(prefix: string, fieldName: string = "Field"): ValidationRule<string> {
61
- return (value: string): string | null => {
62
- if (!value.startsWith(prefix)) {
63
- return `${fieldName} must start with "${prefix}"`;
64
- }
65
- return null;
66
- };
67
- }
68
-
69
- /**
70
- * Validate number is in range
71
- */
72
- export function inRange(min: number, max: number, fieldName: string = "Value"): ValidationRule<number> {
73
- return (value: number): string | null => {
74
- if (typeof value !== "number" || value < min || value > max) {
75
- return `${fieldName} must be between ${min} and ${max}`;
76
- }
77
- return null;
78
- };
79
- }
80
-
81
- /**
82
- * Validate object has required properties
83
- */
84
- export function hasProperties(...props: string[]): ValidationRule<Record<string, unknown>> {
85
- return (value: Record<string, unknown>): string | null => {
86
- const missing = props.filter((prop) => !(prop in value) || value[prop] === undefined);
87
- if (missing.length > 0) {
88
- return `Missing required properties: ${missing.join(", ")}`;
89
- }
90
- return null;
91
- };
92
- }
93
-
94
- /**
95
- * Validate object structure (for schemas)
96
- */
97
- export function isValidSchema(): ValidationRule<Record<string, unknown>> {
98
- return compose(
99
- (schema): string | null => {
100
- if (!schema || typeof schema !== "object") {
101
- return "Schema must be a non-empty object";
102
- }
103
- return null;
104
- },
105
- (schema): string | null => {
106
- if (Object.keys(schema).length === 0) {
107
- return "Schema must contain at least one property";
108
- }
109
- return null;
110
- },
111
- hasProperties("type"),
112
- (schema): string | null => {
113
- const schemaType = schema.type;
114
- if (schemaType !== "object" && schemaType !== "array") {
115
- return `Schema type must be "object" or "array", got "${String(schemaType)}"`;
116
- }
117
- return null;
118
- },
119
- (schema): string | null => {
120
- if (schema.type === "object" && !("properties" in schema)) {
121
- return 'Object schema must have a "properties" field';
122
- }
123
- return null;
124
- }
125
- );
126
- }
127
-
128
- /**
129
- * Helper to validate and throw on error
130
- */
131
- export function validateOrThrow<T>(value: T, rule: ValidationRule<T>): void {
132
- const error = rule(value);
133
- if (error) {
134
- throw new Error(error);
135
- }
136
- }
137
-
138
- /**
139
- * Pre-built composite validators
140
- */
141
- export const validators = {
142
- apiKey: compose(
143
- required("API key"),
144
- minLength(10, "API key")
145
- ),
146
-
147
- modelName: compose(
148
- required("Model name"),
149
- startsWith("gemini-", "Model name")
150
- ),
151
-
152
- prompt: compose(
153
- required("Prompt"),
154
- minLength(3, "Prompt")
155
- ),
156
-
157
- timeout: inRange(1, 300000, "Timeout"),
158
-
159
- schema: isValidSchema(),
160
- };
@@ -1,73 +0,0 @@
1
- /**
2
- * Validation Utilities (Legacy)
3
- * Maintained for backward compatibility
4
- * New code should use validation-composer.util.ts
5
- */
6
-
7
- import { validateOrThrow, validators } from "./validation-composer.util";
8
-
9
- /**
10
- * Validate model name format
11
- * @throws Error if model name is invalid
12
- */
13
- export function validateModelName(modelName: string): void {
14
- validateOrThrow(modelName, validators.modelName);
15
- }
16
-
17
- /**
18
- * Validate API key format
19
- * @throws Error if API key is invalid
20
- */
21
- export function validateApiKey(apiKey: string): void {
22
- validateOrThrow(apiKey, validators.apiKey);
23
- }
24
-
25
- /**
26
- * Validate schema object for structured generation
27
- * @throws Error if schema is invalid
28
- */
29
- export function validateSchema(schema: Record<string, unknown>): void {
30
- validateOrThrow(schema, validators.schema);
31
- }
32
-
33
- /**
34
- * Validate prompt text
35
- * @throws Error if prompt is invalid
36
- */
37
- export function validatePrompt(prompt: string): void {
38
- validateOrThrow(prompt, validators.prompt);
39
- }
40
-
41
- /**
42
- * Validate timeout value
43
- * @throws Error if timeout is invalid
44
- */
45
- export function validateTimeout(timeout: number): void {
46
- validateOrThrow(timeout, validators.timeout);
47
- }
48
-
49
- /**
50
- * Check if a value is a valid object
51
- */
52
- export function isValidObject(value: unknown): value is Record<string, unknown> {
53
- return (
54
- value !== null &&
55
- typeof value === "object" &&
56
- !Array.isArray(value) &&
57
- Object.keys(value).length > 0
58
- );
59
- }
60
-
61
- /**
62
- * Validate required fields in an object
63
- */
64
- export function validateRequiredFields(
65
- obj: Record<string, unknown>,
66
- requiredFields: string[]
67
- ): void {
68
- const missing = requiredFields.filter((field) => !(field in obj) || obj[field] === undefined);
69
-
70
- if (missing.length > 0) {
71
- throw new Error(`Missing required fields: ${missing.join(", ")}`);
72
- }
73
- }