@umituz/react-native-ai-gemini-provider 2.1.13 → 3.0.0
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/index.ts +6 -7
- package/src/infrastructure/services/{base-gemini.service.ts → BaseService.ts} +3 -34
- package/src/infrastructure/services/{gemini-client-core.service.ts → GeminiClient.ts} +10 -25
- package/src/infrastructure/services/GeminiProvider.ts +38 -0
- package/src/infrastructure/services/{gemini-streaming.service.ts → Streaming.ts} +14 -35
- package/src/infrastructure/services/{gemini-structured-text.service.ts → StructuredText.ts} +7 -14
- package/src/infrastructure/services/{gemini-text-generation.service.ts → TextGeneration.ts} +8 -23
- package/src/infrastructure/services/index.ts +6 -21
- package/src/infrastructure/utils/index.ts +7 -13
- package/src/infrastructure/utils/{validation-composer.util.ts → validation.ts} +17 -2
- package/src/presentation/hooks/index.ts +2 -11
- package/src/presentation/hooks/{use-gemini.ts → useGemini.ts} +5 -5
- package/src/providers/ProviderFactory.ts +2 -2
- package/src/infrastructure/services/gemini-provider.ts +0 -62
- package/src/infrastructure/utils/validation.util.ts +0 -73
- /package/src/presentation/hooks/{use-operation-manager.ts → useOperationManager.ts} +0 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -27,13 +27,12 @@ export {
|
|
|
27
27
|
DEFAULT_MODELS
|
|
28
28
|
} from "./domain/entities";
|
|
29
29
|
|
|
30
|
-
//
|
|
31
|
-
export {
|
|
32
|
-
|
|
33
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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
|
|
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 "./
|
|
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
|
|
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) =>
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
60
|
+
export const structuredText = new StructuredTextService();
|
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
import { BaseGeminiService } from "./
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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
|
|
57
|
+
export const textGeneration = new TextGenerationService();
|
|
@@ -1,21 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export { BaseGeminiService } from "./
|
|
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";
|
|
6
|
+
export { BaseGeminiService, type BaseRequestOptions } from "./BaseService";
|
|
@@ -1,9 +1,3 @@
|
|
|
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
1
|
export {
|
|
8
2
|
mapGeminiError,
|
|
9
3
|
isGeminiErrorRetryable,
|
|
@@ -11,14 +5,15 @@ export {
|
|
|
11
5
|
createGeminiError
|
|
12
6
|
} from "./error-mapper.util";
|
|
13
7
|
|
|
14
|
-
// Data transformation (internal)
|
|
15
8
|
export { extractTextFromResponse } from "./gemini-data-transformer.util";
|
|
9
|
+
|
|
16
10
|
export {
|
|
17
11
|
cleanJsonText,
|
|
18
12
|
parseJsonResponse,
|
|
19
13
|
safeParseJson,
|
|
20
14
|
extractJsonFromText
|
|
21
15
|
} from "./json-parser.util";
|
|
16
|
+
|
|
22
17
|
export {
|
|
23
18
|
toSdkContent,
|
|
24
19
|
createTextContent,
|
|
@@ -27,18 +22,17 @@ export {
|
|
|
27
22
|
extractTextFromParts
|
|
28
23
|
} from "./content-mapper.util";
|
|
29
24
|
|
|
30
|
-
// Validation (internal)
|
|
31
25
|
export {
|
|
32
26
|
validateModelName,
|
|
33
27
|
validateApiKey,
|
|
34
28
|
validateSchema,
|
|
35
29
|
validatePrompt,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
validateOrThrow,
|
|
31
|
+
validators,
|
|
32
|
+
compose,
|
|
33
|
+
type ValidationRule,
|
|
34
|
+
} from "./validation";
|
|
40
35
|
|
|
41
|
-
// Async state management (internal)
|
|
42
36
|
export {
|
|
43
37
|
executeWithState,
|
|
44
38
|
type AsyncStateCallbacks,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Validation
|
|
3
|
-
* Composable validation rules for clean, reusable validation
|
|
2
|
+
* Validation utilities
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
export type ValidationRule<T = unknown> = (value: T) => string | null;
|
|
@@ -158,3 +157,19 @@ export const validators = {
|
|
|
158
157
|
|
|
159
158
|
schema: isValidSchema(),
|
|
160
159
|
};
|
|
160
|
+
|
|
161
|
+
export function validateModelName(modelName: string): void {
|
|
162
|
+
validateOrThrow(modelName, validators.modelName);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function validateApiKey(apiKey: string): void {
|
|
166
|
+
validateOrThrow(apiKey, validators.apiKey);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function validateSchema(schema: Record<string, unknown>): void {
|
|
170
|
+
validateOrThrow(schema, validators.schema);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function validatePrompt(prompt: string): void {
|
|
174
|
+
validateOrThrow(prompt, validators.prompt);
|
|
175
|
+
}
|
|
@@ -1,11 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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 { useOperationManager, type OperationManager } from "./useOperationManager";
|
|
2
|
+
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 {
|
|
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 "./
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
-
|
|
43
|
+
geminiClient.initialize(geminiConfig);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
@@ -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,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
|
-
}
|
|
File without changes
|