@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.
- package/package.json +1 -1
- package/src/domain/entities/error.types.ts +0 -17
- package/src/domain/entities/gemini.types.ts +1 -37
- package/src/domain/entities/models.ts +0 -31
- package/src/index.ts +8 -24
- package/src/infrastructure/interceptors/RequestInterceptors.ts +20 -6
- package/src/infrastructure/interceptors/ResponseInterceptors.ts +20 -6
- package/src/infrastructure/services/gemini-client-core.service.ts +25 -32
- package/src/infrastructure/services/gemini-provider.ts +31 -15
- package/src/infrastructure/services/gemini-streaming.service.ts +0 -4
- package/src/infrastructure/services/gemini-structured-text.service.ts +8 -5
- package/src/infrastructure/services/gemini-text-generation.service.ts +20 -51
- package/src/infrastructure/services/index.ts +2 -16
- package/src/infrastructure/telemetry/TelemetryHooks.ts +23 -10
- package/src/infrastructure/utils/async-state.util.ts +0 -7
- package/src/infrastructure/utils/error-mapper.util.ts +13 -8
- package/src/infrastructure/utils/gemini-data-transformer.util.ts +12 -9
- package/src/infrastructure/utils/performance.util.ts +4 -54
- package/src/infrastructure/utils/rate-limiter.util.ts +12 -8
- package/src/presentation/hooks/use-gemini.ts +91 -24
- package/src/providers/ProviderConfig.ts +0 -32
- package/src/providers/ProviderFactory.ts +24 -37
- package/src/providers/index.ts +2 -7
- package/src/domain/README.md +0 -232
- package/src/domain/constants/index.ts +0 -5
- package/src/domain/entities/README.md +0 -238
- package/src/infrastructure/README.md +0 -252
- package/src/infrastructure/cache/CACHE_SYSTEM.md +0 -213
- package/src/infrastructure/cache/README.md +0 -213
- package/src/infrastructure/cache/SimpleCache.ts +0 -173
- package/src/infrastructure/cache/index.ts +0 -7
- package/src/infrastructure/content/ContentBuilder.ts +0 -24
- package/src/infrastructure/content/README.md +0 -175
- package/src/infrastructure/interceptors/README.md +0 -226
- package/src/infrastructure/interceptors/REQUEST_INTERCEPTORS.md +0 -171
- package/src/infrastructure/job/JOB_MANAGER.md +0 -174
- package/src/infrastructure/job/JobManager.ts +0 -114
- package/src/infrastructure/job/README.md +0 -194
- package/src/infrastructure/response/README.md +0 -187
- package/src/infrastructure/response/RESPONSE_FORMATTER.md +0 -185
- package/src/infrastructure/response/ResponseFormatter.ts +0 -58
- package/src/infrastructure/services/generation-executor.ts +0 -71
- package/src/infrastructure/services/job-processor.ts +0 -58
- package/src/infrastructure/services/provider-initializer.ts +0 -31
- package/src/infrastructure/telemetry/README.md +0 -203
- package/src/infrastructure/telemetry/TELEMETRY_SYSTEM.md +0 -200
- package/src/presentation/README.md +0 -187
- package/src/presentation/hooks/README.md +0 -188
- package/src/presentation/hooks/USE_GEMINI_HOOK.md +0 -226
- package/src/providers/README.md +0 -247
package/package.json
CHANGED
|
@@ -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
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
8
|
-
import {
|
|
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
|
-
|
|
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
|
|
21
|
+
return geminiClientCoreService.isInitialized();
|
|
26
22
|
}
|
|
27
23
|
|
|
28
24
|
reset(): void {
|
|
29
|
-
|
|
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
|
-
|
|
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
|
|
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,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
|
-
|
|
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
|
|
36
|
-
return part;
|
|
37
|
-
}),
|
|
24
|
+
parts: content.parts,
|
|
38
25
|
}));
|
|
39
26
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
32
|
+
const response = (result as { response: GeminiResponse }).response;
|
|
47
33
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
57
|
-
candidates: response.candidates?.map((candidate) => ({
|
|
44
|
+
return {
|
|
58
45
|
content: {
|
|
59
|
-
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
|
-
}
|
|
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;
|