@umituz/react-native-ai-gemini-provider 2.1.5 → 2.1.7
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 +11 -104
- package/src/infrastructure/interceptors/BaseInterceptor.ts +78 -0
- package/src/infrastructure/interceptors/RequestInterceptors.ts +6 -62
- package/src/infrastructure/interceptors/ResponseInterceptors.ts +6 -61
- package/src/infrastructure/interceptors/index.ts +7 -13
- package/src/infrastructure/services/base-gemini.service.ts +82 -0
- package/src/infrastructure/services/gemini-streaming.service.ts +31 -61
- package/src/infrastructure/services/gemini-text-generation.service.ts +11 -33
- package/src/infrastructure/services/index.ts +7 -4
- package/src/infrastructure/utils/async/index.ts +1 -10
- package/src/infrastructure/utils/index.ts +43 -26
- package/src/infrastructure/utils/stream-processor.util.ts +156 -0
- package/src/infrastructure/utils/validation-composer.util.ts +160 -0
- package/src/infrastructure/utils/validation.util.ts +9 -68
- package/src/presentation/hooks/index.ts +6 -1
- package/src/presentation/hooks/use-gemini.ts +21 -72
- package/src/presentation/hooks/use-operation-manager.ts +88 -0
- package/src/providers/ConfigBuilder.ts +121 -0
- package/src/providers/ProviderFactory.ts +37 -49
- package/src/providers/index.ts +4 -13
- package/src/infrastructure/utils/async/debounce.util.ts +0 -100
- package/src/infrastructure/utils/async/memoize.util.ts +0 -55
- package/src/infrastructure/utils/env.util.ts +0 -175
- package/src/infrastructure/utils/performance.util.ts +0 -139
- package/src/infrastructure/utils/rate-limiter.util.ts +0 -86
- package/src/infrastructure/utils/retry.util.ts +0 -158
- package/src/providers/ProviderConfig.ts +0 -36
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
import { geminiClientCoreService } from "./gemini-client-core.service";
|
|
1
|
+
import { BaseGeminiService } from "./base-gemini.service";
|
|
3
2
|
import { extractTextFromResponse } from "../utils/gemini-data-transformer.util";
|
|
4
|
-
import {
|
|
3
|
+
import { transformResponse, createTextContent } from "../utils/content-mapper.util";
|
|
5
4
|
import { validatePrompt } from "../utils/validation.util";
|
|
6
|
-
import { createGeminiError } from "../utils/error-mapper.util";
|
|
7
5
|
import type {
|
|
8
6
|
GeminiContent,
|
|
9
7
|
GeminiGenerationConfig,
|
|
10
8
|
GeminiResponse,
|
|
11
9
|
} from "../../domain/entities";
|
|
12
10
|
|
|
13
|
-
class GeminiTextGenerationService {
|
|
11
|
+
class GeminiTextGenerationService extends BaseGeminiService {
|
|
14
12
|
/**
|
|
15
13
|
* Generate content (text, with optional images)
|
|
16
14
|
*
|
|
@@ -23,24 +21,15 @@ class GeminiTextGenerationService {
|
|
|
23
21
|
generationConfig?: GeminiGenerationConfig,
|
|
24
22
|
signal?: AbortSignal,
|
|
25
23
|
): Promise<GeminiResponse> {
|
|
26
|
-
// Validate input
|
|
27
|
-
if (!contents || contents.length === 0) {
|
|
28
|
-
throw new Error("Contents array cannot be empty");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Check for early abort
|
|
32
|
-
if (signal?.aborted) {
|
|
33
|
-
throw new Error("Request was aborted");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
24
|
try {
|
|
37
|
-
const genModel =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const requestOptions = {
|
|
41
|
-
contents: sdkContents as Parameters<typeof genModel.generateContent>[0] extends { contents: infer C } ? C : never,
|
|
25
|
+
const { genModel, sdkContents } = this.validateAndPrepare({
|
|
26
|
+
model,
|
|
27
|
+
contents,
|
|
42
28
|
generationConfig,
|
|
43
|
-
|
|
29
|
+
signal,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const requestOptions = this.createRequestOptions(sdkContents, generationConfig);
|
|
44
33
|
|
|
45
34
|
const result = signal
|
|
46
35
|
? await genModel.generateContent(requestOptions, { signal })
|
|
@@ -54,18 +43,7 @@ class GeminiTextGenerationService {
|
|
|
54
43
|
|
|
55
44
|
return transformResponse(response);
|
|
56
45
|
} catch (error) {
|
|
57
|
-
|
|
58
|
-
if (error instanceof Error && error.name === "GeminiError") {
|
|
59
|
-
throw error;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Check for abort error
|
|
63
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
64
|
-
throw new Error("Request was aborted");
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Wrap other errors
|
|
68
|
-
throw createGeminiError(error);
|
|
46
|
+
return this.handleError(error, "Request was aborted");
|
|
69
47
|
}
|
|
70
48
|
}
|
|
71
49
|
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Infrastructure Services
|
|
3
|
-
* Text-only Gemini services
|
|
2
|
+
* Infrastructure Services - Internal Use Only
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
|
-
//
|
|
5
|
+
// Internal base classes
|
|
6
|
+
export { BaseGeminiService } from "./base-gemini.service";
|
|
7
|
+
export type { BaseRequestOptions } from "./base-gemini.service";
|
|
8
|
+
|
|
9
|
+
// Internal services
|
|
7
10
|
export { geminiClientCoreService } from "./gemini-client-core.service";
|
|
8
11
|
export { geminiTextGenerationService } from "./gemini-text-generation.service";
|
|
9
12
|
export { geminiStructuredTextService } from "./gemini-structured-text.service";
|
|
10
13
|
export { geminiStreamingService } from "./gemini-streaming.service";
|
|
11
14
|
|
|
12
|
-
// Provider
|
|
15
|
+
// Main Provider - Public API
|
|
13
16
|
export {
|
|
14
17
|
geminiProviderService,
|
|
15
18
|
GeminiProvider,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Async
|
|
3
|
-
* Re-exports all async utility functions
|
|
2
|
+
* Async State Management - Internal Use Only
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
export {
|
|
@@ -9,11 +8,3 @@ export {
|
|
|
9
8
|
type AsyncStateSetters,
|
|
10
9
|
type AsyncStateConfig,
|
|
11
10
|
} from "./execute-state.util";
|
|
12
|
-
|
|
13
|
-
export {
|
|
14
|
-
createDebouncedAsync,
|
|
15
|
-
} from "./debounce.util";
|
|
16
|
-
|
|
17
|
-
export {
|
|
18
|
-
createMemoizedAsync,
|
|
19
|
-
} from "./memoize.util";
|
|
@@ -1,30 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Utility Functions - Internal Use Only
|
|
3
|
+
* These are internal implementation details and should not be used directly by consumers
|
|
4
|
+
*/
|
|
3
5
|
|
|
4
|
-
//
|
|
5
|
-
export {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export type { PerformanceMetrics } from "./performance.util";
|
|
12
|
-
|
|
13
|
-
// Rate limiting
|
|
14
|
-
export { RateLimiter } from "./rate-limiter.util";
|
|
15
|
-
export type { RateLimiterOptions } from "./rate-limiter.util";
|
|
6
|
+
// Error handling (internal)
|
|
7
|
+
export {
|
|
8
|
+
mapGeminiError,
|
|
9
|
+
isGeminiErrorRetryable,
|
|
10
|
+
categorizeGeminiError,
|
|
11
|
+
createGeminiError
|
|
12
|
+
} from "./error-mapper.util";
|
|
16
13
|
|
|
17
|
-
//
|
|
18
|
-
export {
|
|
19
|
-
export
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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";
|
|
23
29
|
|
|
24
|
-
//
|
|
25
|
-
export {
|
|
26
|
-
|
|
30
|
+
// Validation (internal)
|
|
31
|
+
export {
|
|
32
|
+
validateModelName,
|
|
33
|
+
validateApiKey,
|
|
34
|
+
validateSchema,
|
|
35
|
+
validatePrompt,
|
|
36
|
+
validateTimeout,
|
|
37
|
+
isValidObject,
|
|
38
|
+
validateRequiredFields
|
|
39
|
+
} from "./validation.util";
|
|
27
40
|
|
|
28
|
-
// Async state management
|
|
29
|
-
export {
|
|
30
|
-
|
|
41
|
+
// Async state management (internal)
|
|
42
|
+
export {
|
|
43
|
+
executeWithState,
|
|
44
|
+
type AsyncStateCallbacks,
|
|
45
|
+
type AsyncStateSetters,
|
|
46
|
+
type AsyncStateConfig
|
|
47
|
+
} from "./async";
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stream Processing Utilities
|
|
3
|
+
* Reusable stream handling logic
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface StreamChunk {
|
|
7
|
+
text: () => string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type ChunkCallback = (text: string) => void;
|
|
11
|
+
export type ErrorLogger = (error: unknown, context?: string) => void;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Process async stream with chunk callback
|
|
15
|
+
*/
|
|
16
|
+
export async function processStream(
|
|
17
|
+
stream: AsyncIterable<StreamChunk>,
|
|
18
|
+
onChunk: ChunkCallback,
|
|
19
|
+
onError?: ErrorLogger
|
|
20
|
+
): Promise<string> {
|
|
21
|
+
let fullText = "";
|
|
22
|
+
|
|
23
|
+
for await (const chunk of stream) {
|
|
24
|
+
try {
|
|
25
|
+
const chunkText = chunk.text();
|
|
26
|
+
if (chunkText) {
|
|
27
|
+
fullText += chunkText;
|
|
28
|
+
safeCallChunk(onChunk, chunkText, onError);
|
|
29
|
+
}
|
|
30
|
+
} catch (chunkError) {
|
|
31
|
+
logError(onError, chunkError, "stream-chunk");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return fullText;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Safely call chunk callback without breaking the stream
|
|
40
|
+
*/
|
|
41
|
+
function safeCallChunk(
|
|
42
|
+
callback: ChunkCallback,
|
|
43
|
+
text: string,
|
|
44
|
+
onError?: ErrorLogger
|
|
45
|
+
): void {
|
|
46
|
+
try {
|
|
47
|
+
callback(text);
|
|
48
|
+
} catch (callbackError) {
|
|
49
|
+
logError(onError, callbackError, "stream-callback");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Log error without breaking the stream
|
|
55
|
+
*/
|
|
56
|
+
function logError(
|
|
57
|
+
onError: ErrorLogger | undefined,
|
|
58
|
+
error: unknown,
|
|
59
|
+
context?: string
|
|
60
|
+
): void {
|
|
61
|
+
if (onError) {
|
|
62
|
+
try {
|
|
63
|
+
onError(error, context);
|
|
64
|
+
} catch {
|
|
65
|
+
// Silently ignore logger errors
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
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
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
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,32 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Validation Utilities
|
|
3
|
-
*
|
|
2
|
+
* Validation Utilities (Legacy)
|
|
3
|
+
* Maintained for backward compatibility
|
|
4
|
+
* New code should use validation-composer.util.ts
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
const MAX_TIMEOUT_MS = 300000;
|
|
8
|
-
|
|
9
|
-
/** Minimum prompt length */
|
|
10
|
-
const MIN_PROMPT_LENGTH = 3;
|
|
7
|
+
import { validateOrThrow, validators } from "./validation-composer.util";
|
|
11
8
|
|
|
12
9
|
/**
|
|
13
10
|
* Validate model name format
|
|
14
11
|
* @throws Error if model name is invalid
|
|
15
12
|
*/
|
|
16
13
|
export function validateModelName(modelName: string): void {
|
|
17
|
-
|
|
18
|
-
const displayName: string = modelName === null ? "null" : typeof modelName;
|
|
19
|
-
throw new Error(
|
|
20
|
-
`Invalid model name: "${displayName}". Model name must be a non-empty string.`
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Check for valid model format (starts with gemini-)
|
|
25
|
-
if (!modelName.startsWith("gemini-")) {
|
|
26
|
-
throw new Error(
|
|
27
|
-
`Invalid model name: "${modelName}". Gemini models should start with "gemini-".`
|
|
28
|
-
);
|
|
29
|
-
}
|
|
14
|
+
validateOrThrow(modelName, validators.modelName);
|
|
30
15
|
}
|
|
31
16
|
|
|
32
17
|
/**
|
|
@@ -34,16 +19,7 @@ export function validateModelName(modelName: string): void {
|
|
|
34
19
|
* @throws Error if API key is invalid
|
|
35
20
|
*/
|
|
36
21
|
export function validateApiKey(apiKey: string): void {
|
|
37
|
-
|
|
38
|
-
throw new Error("API key must be a non-empty string");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Gemini API keys typically start with "AIza"
|
|
42
|
-
// This is a soft validation - actual validation happens on the API side
|
|
43
|
-
const trimmedKey = apiKey.trim();
|
|
44
|
-
if (trimmedKey.length < 10) {
|
|
45
|
-
throw new Error("API key appears to be invalid (too short)");
|
|
46
|
-
}
|
|
22
|
+
validateOrThrow(apiKey, validators.apiKey);
|
|
47
23
|
}
|
|
48
24
|
|
|
49
25
|
/**
|
|
@@ -51,30 +27,7 @@ export function validateApiKey(apiKey: string): void {
|
|
|
51
27
|
* @throws Error if schema is invalid
|
|
52
28
|
*/
|
|
53
29
|
export function validateSchema(schema: Record<string, unknown>): void {
|
|
54
|
-
|
|
55
|
-
throw new Error("Schema must be a non-empty object");
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (Object.keys(schema).length === 0) {
|
|
59
|
-
throw new Error("Schema must contain at least one property");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Basic structure validation - schema should have type
|
|
63
|
-
if (!("type" in schema)) {
|
|
64
|
-
throw new Error('Schema must have a "type" property (e.g., "object")');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const schemaType = schema.type;
|
|
68
|
-
|
|
69
|
-
if (schemaType !== "object" && schemaType !== "array") {
|
|
70
|
-
const typeStr = String(schemaType);
|
|
71
|
-
throw new Error(`Schema type must be "object" or "array", got "${typeStr}"`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// If type is object, should have properties
|
|
75
|
-
if (schemaType === "object" && !("properties" in schema)) {
|
|
76
|
-
throw new Error('Object schema must have a "properties" field');
|
|
77
|
-
}
|
|
30
|
+
validateOrThrow(schema, validators.schema);
|
|
78
31
|
}
|
|
79
32
|
|
|
80
33
|
/**
|
|
@@ -82,13 +35,7 @@ export function validateSchema(schema: Record<string, unknown>): void {
|
|
|
82
35
|
* @throws Error if prompt is invalid
|
|
83
36
|
*/
|
|
84
37
|
export function validatePrompt(prompt: string): void {
|
|
85
|
-
|
|
86
|
-
throw new Error("Prompt must be a non-empty string");
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (prompt.trim().length < MIN_PROMPT_LENGTH) {
|
|
90
|
-
throw new Error(`Prompt is too short (minimum ${MIN_PROMPT_LENGTH} characters)`);
|
|
91
|
-
}
|
|
38
|
+
validateOrThrow(prompt, validators.prompt);
|
|
92
39
|
}
|
|
93
40
|
|
|
94
41
|
/**
|
|
@@ -96,13 +43,7 @@ export function validatePrompt(prompt: string): void {
|
|
|
96
43
|
* @throws Error if timeout is invalid
|
|
97
44
|
*/
|
|
98
45
|
export function validateTimeout(timeout: number): void {
|
|
99
|
-
|
|
100
|
-
throw new Error("Timeout must be a positive number");
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (timeout > MAX_TIMEOUT_MS) {
|
|
104
|
-
throw new Error(`Timeout cannot exceed ${MAX_TIMEOUT_MS}ms (5 minutes)`);
|
|
105
|
-
}
|
|
46
|
+
validateOrThrow(timeout, validators.timeout);
|
|
106
47
|
}
|
|
107
48
|
|
|
108
49
|
/**
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* React Hooks - Public API
|
|
3
3
|
*/
|
|
4
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
|
|
5
10
|
export { useGemini } from "./use-gemini";
|
|
6
11
|
export type { UseGeminiOptions, UseGeminiReturn } from "./use-gemini";
|