@umituz/react-native-ai-groq-provider 1.0.24 → 1.0.26
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/application/use-cases/chat-session.usecase.ts +62 -14
- package/src/application/use-cases/streaming.usecase.ts +13 -7
- package/src/application/use-cases/structured-generation.usecase.ts +27 -10
- package/src/application/use-cases/text-generation.usecase.ts +4 -3
- package/src/domain/entities/error.types.ts +17 -2
- package/src/index.ts +24 -66
- package/src/infrastructure/http/groq-http-client.ts +68 -12
- package/src/infrastructure/http/streaming-client.ts +139 -87
- package/src/infrastructure/telemetry/TelemetryHooks.ts +39 -19
- package/src/infrastructure/utils/calculation.util.ts +59 -63
- package/src/infrastructure/utils/content-mapper.util.ts +1 -1
- package/src/presentation/hooks/use-groq.hook.ts +58 -41
- package/src/providers/ConfigBuilder.ts +2 -73
- package/src/providers/ProviderFactory.ts +7 -62
- package/src/shared/request-builder.ts +29 -10
- package/src/shared/response-handler.ts +93 -0
- package/src/types/react-native-global.d.ts +12 -0
- package/src/application/use-cases/index.ts +0 -19
- package/src/domain/entities/index.ts +0 -7
- package/src/infrastructure/http/index.ts +0 -7
- package/src/infrastructure/telemetry/index.ts +0 -5
- package/src/infrastructure/utils/async/index.ts +0 -6
- package/src/infrastructure/utils/index.ts +0 -8
- package/src/presentation/hooks/index.ts +0 -7
- package/src/presentation/hooks/useGroq.ts +0 -235
- package/src/presentation/hooks/useOperationManager.ts +0 -119
- package/src/presentation/index.ts +0 -6
- package/src/providers/index.ts +0 -16
- package/src/shared/index.ts +0 -16
package/package.json
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
* Manages multi-turn chat conversations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { GroqMessage, GroqChatConfig } from "../../domain/entities";
|
|
7
|
-
import {
|
|
6
|
+
import type { GroqMessage, GroqChatConfig } from "../../domain/entities/groq.types";
|
|
7
|
+
import { DEFAULT_MODELS } from "../../domain/entities/groq.types";
|
|
8
|
+
import { groqHttpClient } from "../../infrastructure/http/groq-http-client";
|
|
8
9
|
import { RequestBuilder } from "../../shared/request-builder";
|
|
9
10
|
import { ResponseHandler } from "../../shared/response-handler";
|
|
10
11
|
import { logger } from "../../shared/logger";
|
|
11
12
|
import { GroqError, GroqErrorType } from "../../domain/entities/error.types";
|
|
12
|
-
import { generateSessionId } from "../../utils/calculation.util";
|
|
13
|
+
import { generateSessionId } from "../../infrastructure/utils/calculation.util";
|
|
13
14
|
|
|
14
15
|
export interface ChatSession {
|
|
15
16
|
id: string;
|
|
@@ -34,13 +35,19 @@ class ChatSessionManager {
|
|
|
34
35
|
private sessions = new Map<string, ChatSession>();
|
|
35
36
|
private readonly MAX_SESSIONS = 100;
|
|
36
37
|
private readonly SESSION_TTL_MS = 24 * 60 * 60 * 1000;
|
|
38
|
+
private oldestSessionId: string | null = null;
|
|
39
|
+
private cleanupScheduled = false;
|
|
40
|
+
private readonly CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
|
37
41
|
|
|
38
42
|
create(config: GroqChatConfig = {}): ChatSession {
|
|
39
|
-
|
|
43
|
+
// Lazy cleanup - only check when needed, not every time
|
|
44
|
+
if (this.sessions.size >= this.MAX_SESSIONS || !this.cleanupScheduled) {
|
|
45
|
+
this.scheduleCleanup();
|
|
46
|
+
}
|
|
40
47
|
|
|
41
48
|
const session: ChatSession = {
|
|
42
49
|
id: generateSessionId("groq-chat"),
|
|
43
|
-
model: config.model ||
|
|
50
|
+
model: config.model || DEFAULT_MODELS.TEXT,
|
|
44
51
|
systemInstruction: config.systemInstruction,
|
|
45
52
|
messages: config.history ? [...config.history] : [],
|
|
46
53
|
createdAt: new Date(),
|
|
@@ -49,12 +56,16 @@ class ChatSessionManager {
|
|
|
49
56
|
|
|
50
57
|
this.sessions.set(session.id, session);
|
|
51
58
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
.
|
|
59
|
+
// Track oldest session for O(1) removal
|
|
60
|
+
if (!this.oldestSessionId ||
|
|
61
|
+
this.sessions.get(this.oldestSessionId)!.createdAt > session.createdAt) {
|
|
62
|
+
this.oldestSessionId = session.id;
|
|
63
|
+
}
|
|
55
64
|
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
// Fast path: if at limit, just remove oldest
|
|
66
|
+
if (this.sessions.size > this.MAX_SESSIONS && this.oldestSessionId) {
|
|
67
|
+
this.sessions.delete(this.oldestSessionId);
|
|
68
|
+
this.updateOldestSessionId();
|
|
58
69
|
}
|
|
59
70
|
|
|
60
71
|
return session;
|
|
@@ -65,21 +76,53 @@ class ChatSessionManager {
|
|
|
65
76
|
}
|
|
66
77
|
|
|
67
78
|
delete(sessionId: string): boolean {
|
|
68
|
-
|
|
79
|
+
const deleted = this.sessions.delete(sessionId);
|
|
80
|
+
if (deleted && sessionId === this.oldestSessionId) {
|
|
81
|
+
this.updateOldestSessionId();
|
|
82
|
+
}
|
|
83
|
+
return deleted;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private scheduleCleanup(): void {
|
|
87
|
+
if (this.cleanupScheduled) return;
|
|
88
|
+
|
|
89
|
+
this.cleanupScheduled = true;
|
|
90
|
+
// Schedule cleanup for next idle time
|
|
91
|
+
setTimeout(() => {
|
|
92
|
+
this.cleanupOldSessions();
|
|
93
|
+
this.cleanupScheduled = false;
|
|
94
|
+
}, this.CLEANUP_INTERVAL_MS);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private updateOldestSessionId(): void {
|
|
98
|
+
let oldest: Date | null = null;
|
|
99
|
+
let oldestId: string | null = null;
|
|
100
|
+
|
|
101
|
+
for (const [id, session] of this.sessions.entries()) {
|
|
102
|
+
if (!oldest || session.createdAt < oldest) {
|
|
103
|
+
oldest = session.createdAt;
|
|
104
|
+
oldestId = id;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.oldestSessionId = oldestId;
|
|
69
109
|
}
|
|
70
110
|
|
|
71
111
|
private cleanupOldSessions(): void {
|
|
72
112
|
const now = Date.now();
|
|
73
|
-
|
|
113
|
+
let removed = 0;
|
|
74
114
|
|
|
75
115
|
for (const [id, session] of this.sessions.entries()) {
|
|
76
116
|
const age = now - session.updatedAt.getTime();
|
|
77
117
|
if (age > this.SESSION_TTL_MS) {
|
|
78
|
-
|
|
118
|
+
this.sessions.delete(id);
|
|
119
|
+
removed++;
|
|
79
120
|
}
|
|
80
121
|
}
|
|
81
122
|
|
|
82
|
-
|
|
123
|
+
if (removed > 0) {
|
|
124
|
+
this.updateOldestSessionId();
|
|
125
|
+
}
|
|
83
126
|
}
|
|
84
127
|
|
|
85
128
|
async send(sessionId: string, content: string): Promise<ChatSendResult> {
|
|
@@ -94,6 +137,11 @@ class ChatSessionManager {
|
|
|
94
137
|
const userMessage: GroqMessage = { role: "user", content };
|
|
95
138
|
session.messages.push(userMessage);
|
|
96
139
|
|
|
140
|
+
// Prevent unbounded memory growth
|
|
141
|
+
if (session.messages.length > 100) {
|
|
142
|
+
session.messages = session.messages.slice(-50); // Keep last 50
|
|
143
|
+
}
|
|
144
|
+
|
|
97
145
|
const messages = this.buildMessages(session);
|
|
98
146
|
const request = RequestBuilder.buildChatRequest(messages, {
|
|
99
147
|
model: session.model,
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* Handles streaming text generation
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { GroqGenerationConfig } from "../../domain/entities";
|
|
7
|
-
import { streamChatCompletion } from "../../infrastructure/http";
|
|
6
|
+
import type { GroqGenerationConfig } from "../../domain/entities/groq.types";
|
|
7
|
+
import { streamChatCompletion } from "../../infrastructure/http/streaming-client";
|
|
8
8
|
import { RequestBuilder } from "../../shared/request-builder";
|
|
9
9
|
import { logger } from "../../shared/logger";
|
|
10
|
+
import { groqHttpClient } from "../../infrastructure/http/groq-http-client";
|
|
10
11
|
|
|
11
12
|
export interface StreamingCallbacks {
|
|
12
13
|
onChunk?: (chunk: string) => void;
|
|
@@ -29,16 +30,21 @@ export async function* streamText(
|
|
|
29
30
|
promptLength: prompt.length,
|
|
30
31
|
});
|
|
31
32
|
|
|
33
|
+
if (!groqHttpClient.isInitialized()) {
|
|
34
|
+
throw new Error("Groq client not initialized. Call initializeProvider() first.");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const config = groqHttpClient.getConfig();
|
|
32
38
|
const request = RequestBuilder.buildPromptRequest(prompt, options);
|
|
33
|
-
const config = {
|
|
34
|
-
apiKey: "", // Will be set by factory
|
|
35
|
-
baseUrl: "", // Will be set by factory
|
|
36
|
-
};
|
|
37
39
|
|
|
38
40
|
let fullContent = "";
|
|
39
41
|
|
|
40
42
|
try {
|
|
41
|
-
for await (const chunk of streamChatCompletion(request,
|
|
43
|
+
for await (const chunk of streamChatCompletion(request, {
|
|
44
|
+
apiKey: config.apiKey,
|
|
45
|
+
baseUrl: config.baseUrl,
|
|
46
|
+
timeoutMs: config.timeoutMs,
|
|
47
|
+
})) {
|
|
42
48
|
const content = chunk.choices[0]?.delta?.content;
|
|
43
49
|
if (content) {
|
|
44
50
|
fullContent += content;
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Structured Generation Use Case
|
|
3
3
|
* Generates structured JSON output from prompts
|
|
4
|
+
* Optimized for performance
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
import type { GroqGenerationConfig
|
|
7
|
-
import { groqHttpClient } from "../../infrastructure/http";
|
|
7
|
+
import type { GroqGenerationConfig } from "../../domain/entities/groq.types";
|
|
8
|
+
import { groqHttpClient } from "../../infrastructure/http/groq-http-client";
|
|
8
9
|
import { RequestBuilder } from "../../shared/request-builder";
|
|
10
|
+
import { Timer } from "../../shared/timer";
|
|
11
|
+
import { logger } from "../../shared/logger";
|
|
9
12
|
import { ResponseHandler } from "../../shared/response-handler";
|
|
10
|
-
import { Timer, logger } from "../../shared/logger";
|
|
11
13
|
import { GroqError, GroqErrorType } from "../../domain/entities/error.types";
|
|
12
|
-
import { cleanJsonResponse } from "../../utils/content-mapper.util";
|
|
14
|
+
import { cleanJsonResponse } from "../../infrastructure/utils/content-mapper.util";
|
|
15
|
+
|
|
16
|
+
const MAX_CONTENT_LENGTH_FOR_ERROR = 200;
|
|
13
17
|
|
|
14
18
|
export interface StructuredGenerationOptions<T> {
|
|
15
19
|
model?: string;
|
|
@@ -54,6 +58,10 @@ export async function generateStructured<T = Record<string, unknown>>(
|
|
|
54
58
|
try {
|
|
55
59
|
const parsed = JSON.parse(content) as T;
|
|
56
60
|
|
|
61
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
62
|
+
throw new Error("Response is not a valid object");
|
|
63
|
+
}
|
|
64
|
+
|
|
57
65
|
logger.debug("StructuredGeneration", "Complete", {
|
|
58
66
|
totalDuration: Timer.format(result.totalMs),
|
|
59
67
|
parsedKeys: Object.keys(parsed),
|
|
@@ -66,9 +74,13 @@ export async function generateStructured<T = Record<string, unknown>>(
|
|
|
66
74
|
contentLength: content.length,
|
|
67
75
|
});
|
|
68
76
|
|
|
77
|
+
const truncatedContent = content.length > MAX_CONTENT_LENGTH_FOR_ERROR
|
|
78
|
+
? content.substring(0, MAX_CONTENT_LENGTH_FOR_ERROR) + "..."
|
|
79
|
+
: content;
|
|
80
|
+
|
|
69
81
|
throw new GroqError(
|
|
70
82
|
GroqErrorType.UNKNOWN_ERROR,
|
|
71
|
-
`Failed to parse JSON: ${
|
|
83
|
+
`Failed to parse JSON response: ${truncatedContent}`,
|
|
72
84
|
error
|
|
73
85
|
);
|
|
74
86
|
}
|
|
@@ -80,12 +92,17 @@ function buildSystemPrompt<T>(
|
|
|
80
92
|
): string {
|
|
81
93
|
let prompt = "You are a helpful assistant that generates valid JSON output.";
|
|
82
94
|
|
|
83
|
-
if (schema) {
|
|
84
|
-
prompt +=
|
|
85
|
-
|
|
95
|
+
if (schema || example) {
|
|
96
|
+
prompt += "\n\nResponse requirements:";
|
|
97
|
+
|
|
98
|
+
if (schema) {
|
|
99
|
+
// Use compact JSON to reduce tokens and improve speed
|
|
100
|
+
prompt += `\nSchema: ${JSON.stringify(schema)}`;
|
|
101
|
+
}
|
|
86
102
|
|
|
87
|
-
|
|
88
|
-
|
|
103
|
+
if (example) {
|
|
104
|
+
prompt += `\nExample: ${JSON.stringify(example)}`;
|
|
105
|
+
}
|
|
89
106
|
}
|
|
90
107
|
|
|
91
108
|
prompt += "\n\nIMPORTANT: Respond ONLY with valid JSON. No markdown, no code blocks.";
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
* Handles simple text generation from prompts
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { GroqGenerationConfig } from "../../domain/entities";
|
|
7
|
-
import { groqHttpClient } from "../../infrastructure/http";
|
|
6
|
+
import type { GroqGenerationConfig } from "../../domain/entities/groq.types";
|
|
7
|
+
import { groqHttpClient } from "../../infrastructure/http/groq-http-client";
|
|
8
8
|
import { RequestBuilder } from "../../shared/request-builder";
|
|
9
9
|
import { ResponseHandler } from "../../shared/response-handler";
|
|
10
|
-
import { Timer
|
|
10
|
+
import { Timer } from "../../shared/timer";
|
|
11
|
+
import { logger } from "../../shared/logger";
|
|
11
12
|
|
|
12
13
|
export interface TextGenerationOptions {
|
|
13
14
|
model?: string;
|
|
@@ -22,7 +22,7 @@ export enum GroqErrorType {
|
|
|
22
22
|
MISSING_CONFIG = "MISSING_CONFIG",
|
|
23
23
|
/** Network error occurred */
|
|
24
24
|
NETWORK_ERROR = "NETWORK_ERROR",
|
|
25
|
-
/** Request was aborted */
|
|
25
|
+
/** Request was aborted or timed out */
|
|
26
26
|
ABORT_ERROR = "ABORT_ERROR",
|
|
27
27
|
/** Rate limit exceeded */
|
|
28
28
|
RATE_LIMIT_ERROR = "RATE_LIMIT_ERROR",
|
|
@@ -30,6 +30,8 @@ export enum GroqErrorType {
|
|
|
30
30
|
QUOTA_EXCEEDED = "QUOTA_EXCEEDED",
|
|
31
31
|
/** Server error */
|
|
32
32
|
SERVER_ERROR = "SERVER_ERROR",
|
|
33
|
+
/** Client error (invalid request) */
|
|
34
|
+
CLIENT_ERROR = "CLIENT_ERROR",
|
|
33
35
|
/** Unknown error occurred */
|
|
34
36
|
UNKNOWN_ERROR = "UNKNOWN_ERROR",
|
|
35
37
|
}
|
|
@@ -38,17 +40,30 @@ export enum GroqErrorType {
|
|
|
38
40
|
* Map HTTP status codes to error types
|
|
39
41
|
*/
|
|
40
42
|
export function mapHttpStatusToErrorType(status: number): GroqErrorType {
|
|
43
|
+
// Authentication errors
|
|
41
44
|
if (status === 401 || status === 403) {
|
|
42
45
|
return GroqErrorType.INVALID_API_KEY;
|
|
43
46
|
}
|
|
47
|
+
|
|
48
|
+
// Rate limiting
|
|
44
49
|
if (status === 429) {
|
|
45
50
|
return GroqErrorType.RATE_LIMIT_ERROR;
|
|
46
51
|
}
|
|
52
|
+
|
|
53
|
+
// Quota exceeded
|
|
54
|
+
if (status === 402) {
|
|
55
|
+
return GroqErrorType.QUOTA_EXCEEDED;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Server errors
|
|
47
59
|
if (status >= 500 && status < 600) {
|
|
48
60
|
return GroqErrorType.SERVER_ERROR;
|
|
49
61
|
}
|
|
62
|
+
|
|
63
|
+
// Client errors (4xx except specific cases above)
|
|
50
64
|
if (status >= 400 && status < 500) {
|
|
51
|
-
return GroqErrorType.
|
|
65
|
+
return GroqErrorType.CLIENT_ERROR;
|
|
52
66
|
}
|
|
67
|
+
|
|
53
68
|
return GroqErrorType.UNKNOWN_ERROR;
|
|
54
69
|
}
|
package/src/index.ts
CHANGED
|
@@ -2,18 +2,11 @@
|
|
|
2
2
|
* @umituz/react-native-ai-groq-provider
|
|
3
3
|
* Groq text generation provider for React Native applications
|
|
4
4
|
*
|
|
5
|
-
* DDD Architecture:
|
|
6
|
-
* - Domain: Core entities and types
|
|
7
|
-
* - Application: Use cases and business logic
|
|
8
|
-
* - Infrastructure: External services and HTTP clients
|
|
9
|
-
* - Presentation: React hooks and UI utilities
|
|
10
|
-
* - Shared: Common utilities
|
|
11
|
-
*
|
|
12
5
|
* @author umituz
|
|
13
6
|
* @license MIT
|
|
14
7
|
*/
|
|
15
8
|
|
|
16
|
-
// Domain Layer
|
|
9
|
+
// Domain Layer - Direct exports, no barrel re-exports
|
|
17
10
|
export type {
|
|
18
11
|
GroqConfig,
|
|
19
12
|
GroqGenerationConfig,
|
|
@@ -28,12 +21,9 @@ export type {
|
|
|
28
21
|
GroqChunkChoice,
|
|
29
22
|
GroqErrorResponse,
|
|
30
23
|
GroqChatConfig,
|
|
31
|
-
} from "./domain/entities";
|
|
24
|
+
} from "./domain/entities/groq.types";
|
|
32
25
|
|
|
33
|
-
export {
|
|
34
|
-
GROQ_MODELS,
|
|
35
|
-
DEFAULT_MODELS,
|
|
36
|
-
} from "./domain/entities";
|
|
26
|
+
export { GROQ_MODELS, DEFAULT_MODELS } from "./domain/entities/groq.types";
|
|
37
27
|
|
|
38
28
|
export {
|
|
39
29
|
GroqError,
|
|
@@ -49,59 +39,30 @@ export {
|
|
|
49
39
|
type ModelInfo,
|
|
50
40
|
} from "./domain/entities/models";
|
|
51
41
|
|
|
52
|
-
// Application Layer
|
|
53
|
-
export {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
chatSessionManager,
|
|
58
|
-
type TextGenerationOptions,
|
|
59
|
-
type StructuredGenerationOptions,
|
|
60
|
-
type StreamingCallbacks,
|
|
61
|
-
type StreamingOptions,
|
|
62
|
-
type ChatSession,
|
|
63
|
-
type ChatSendResult,
|
|
64
|
-
} from "./application/use-cases";
|
|
42
|
+
// Application Layer - Direct exports
|
|
43
|
+
export { generateText, type TextGenerationOptions } from "./application/use-cases/text-generation.usecase";
|
|
44
|
+
export { generateStructured, type StructuredGenerationOptions } from "./application/use-cases/structured-generation.usecase";
|
|
45
|
+
export { streamText, type StreamingCallbacks, type StreamingOptions } from "./application/use-cases/streaming.usecase";
|
|
46
|
+
export { chatSessionManager, type ChatSession, type ChatSendResult } from "./application/use-cases/chat-session.usecase";
|
|
65
47
|
|
|
66
|
-
// Infrastructure Layer
|
|
67
|
-
export {
|
|
68
|
-
|
|
69
|
-
streamChatCompletion,
|
|
70
|
-
} from "./infrastructure/http";
|
|
48
|
+
// Infrastructure Layer - Direct exports
|
|
49
|
+
export { groqHttpClient } from "./infrastructure/http/groq-http-client";
|
|
50
|
+
export { streamChatCompletion } from "./infrastructure/http/streaming-client";
|
|
71
51
|
|
|
72
|
-
// Presentation Layer
|
|
73
|
-
export {
|
|
74
|
-
useGroq,
|
|
75
|
-
type UseGroqOptions,
|
|
76
|
-
type UseGroqReturn,
|
|
77
|
-
} from "./presentation";
|
|
52
|
+
// Presentation Layer - Direct exports
|
|
53
|
+
export { useGroq, type UseGroqOptions, type UseGroqReturn } from "./presentation/hooks/use-groq.hook";
|
|
78
54
|
|
|
79
|
-
// Shared Layer
|
|
80
|
-
export {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
RequestBuilder,
|
|
85
|
-
ResponseHandler,
|
|
86
|
-
type LogContext,
|
|
87
|
-
type TimerResult,
|
|
88
|
-
type RequestBuilderOptions,
|
|
89
|
-
type ResponseHandlerResult,
|
|
90
|
-
} from "./shared";
|
|
55
|
+
// Shared Layer - Direct exports
|
|
56
|
+
export { logger, LogLevel, type LogContext } from "./shared/logger";
|
|
57
|
+
export { Timer, type TimerResult } from "./shared/timer";
|
|
58
|
+
export { RequestBuilder, type RequestBuilderOptions } from "./shared/request-builder";
|
|
59
|
+
export { ResponseHandler, type ResponseHandlerResult } from "./shared/response-handler";
|
|
91
60
|
|
|
92
|
-
// Provider Factory
|
|
93
|
-
export {
|
|
94
|
-
|
|
95
|
-
GenerationConfigBuilder,
|
|
96
|
-
providerFactory,
|
|
97
|
-
initializeProvider,
|
|
98
|
-
configureProvider,
|
|
99
|
-
resetProvider,
|
|
100
|
-
type ProviderConfig,
|
|
101
|
-
type ProviderFactoryOptions,
|
|
102
|
-
} from "./providers/ProviderFactory";
|
|
61
|
+
// Provider Factory - Direct exports, no wrappers
|
|
62
|
+
export { ConfigBuilder, GenerationConfigBuilder } from "./providers/ConfigBuilder";
|
|
63
|
+
export { initializeProvider, resetProvider, isProviderInitialized, type ProviderConfig } from "./providers/ProviderFactory";
|
|
103
64
|
|
|
104
|
-
// Utilities
|
|
65
|
+
// Utilities - Direct exports, no barrel re-exports
|
|
105
66
|
export {
|
|
106
67
|
createUserMessage,
|
|
107
68
|
createAssistantMessage,
|
|
@@ -125,7 +86,7 @@ export {
|
|
|
125
86
|
executeWithRetry,
|
|
126
87
|
type AsyncStateSetters,
|
|
127
88
|
type AsyncCallbacks,
|
|
128
|
-
} from "./infrastructure/utils/async";
|
|
89
|
+
} from "./infrastructure/utils/async/execute-state.util";
|
|
129
90
|
|
|
130
91
|
export {
|
|
131
92
|
generateRandomId,
|
|
@@ -143,7 +104,4 @@ export {
|
|
|
143
104
|
calculateAverage,
|
|
144
105
|
} from "./infrastructure/utils/calculation.util";
|
|
145
106
|
|
|
146
|
-
export {
|
|
147
|
-
telemetry,
|
|
148
|
-
useTelemetry,
|
|
149
|
-
} from "./infrastructure/telemetry";
|
|
107
|
+
export { telemetry, useTelemetry } from "./infrastructure/telemetry/TelemetryHooks";
|
|
@@ -7,13 +7,14 @@ import type {
|
|
|
7
7
|
GroqConfig,
|
|
8
8
|
GroqChatRequest,
|
|
9
9
|
GroqChatResponse,
|
|
10
|
-
|
|
11
|
-
} from "../../domain/entities";
|
|
10
|
+
} from "../../domain/entities/groq.types";
|
|
12
11
|
import { GroqError, GroqErrorType, mapHttpStatusToErrorType } from "../../domain/entities/error.types";
|
|
13
12
|
import { logger } from "../../shared/logger";
|
|
14
13
|
|
|
15
14
|
const DEFAULT_BASE_URL = "https://api.groq.com/openai/v1";
|
|
16
15
|
const DEFAULT_TIMEOUT = 60000;
|
|
16
|
+
const MIN_API_KEY_LENGTH = 10;
|
|
17
|
+
const GROQ_KEY_PREFIX = "gsk_";
|
|
17
18
|
|
|
18
19
|
class GroqHttpClient {
|
|
19
20
|
private config: GroqConfig | null = null;
|
|
@@ -28,17 +29,30 @@ class GroqHttpClient {
|
|
|
28
29
|
baseUrl: config.baseUrl || DEFAULT_BASE_URL,
|
|
29
30
|
});
|
|
30
31
|
|
|
31
|
-
if (!apiKey
|
|
32
|
+
if (!apiKey) {
|
|
32
33
|
throw new GroqError(
|
|
33
34
|
GroqErrorType.INVALID_API_KEY,
|
|
34
|
-
"API key is required
|
|
35
|
+
"API key is required"
|
|
35
36
|
);
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
if (apiKey.length < MIN_API_KEY_LENGTH) {
|
|
40
|
+
throw new GroqError(
|
|
41
|
+
GroqErrorType.INVALID_API_KEY,
|
|
42
|
+
`API key must be at least ${MIN_API_KEY_LENGTH} characters`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!apiKey.startsWith(GROQ_KEY_PREFIX)) {
|
|
47
|
+
logger.warn("GroqClient", "API key does not start with expected prefix", {
|
|
48
|
+
prefix: GROQ_KEY_PREFIX,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
38
52
|
this.config = {
|
|
39
53
|
apiKey,
|
|
40
|
-
baseUrl: config.baseUrl
|
|
41
|
-
timeoutMs: config.timeoutMs
|
|
54
|
+
baseUrl: this.normalizeBaseUrl(config.baseUrl),
|
|
55
|
+
timeoutMs: this.validateTimeout(config.timeoutMs),
|
|
42
56
|
textModel: config.textModel,
|
|
43
57
|
};
|
|
44
58
|
this.initialized = true;
|
|
@@ -48,6 +62,28 @@ class GroqHttpClient {
|
|
|
48
62
|
});
|
|
49
63
|
}
|
|
50
64
|
|
|
65
|
+
private normalizeBaseUrl(baseUrl?: string): string {
|
|
66
|
+
if (!baseUrl) {
|
|
67
|
+
return DEFAULT_BASE_URL;
|
|
68
|
+
}
|
|
69
|
+
return baseUrl.replace(/\/+$/, ""); // Remove trailing slashes
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private validateTimeout(timeout?: number): number {
|
|
73
|
+
const DEFAULT = DEFAULT_TIMEOUT;
|
|
74
|
+
if (timeout === undefined || timeout === null) {
|
|
75
|
+
return DEFAULT;
|
|
76
|
+
}
|
|
77
|
+
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
78
|
+
logger.warn("GroqClient", "Invalid timeout, using default", {
|
|
79
|
+
provided: timeout,
|
|
80
|
+
default: DEFAULT,
|
|
81
|
+
});
|
|
82
|
+
return DEFAULT;
|
|
83
|
+
}
|
|
84
|
+
return Math.min(timeout, 300000); // Cap at 5 minutes
|
|
85
|
+
}
|
|
86
|
+
|
|
51
87
|
isInitialized(): boolean {
|
|
52
88
|
return this.initialized && this.config !== null;
|
|
53
89
|
}
|
|
@@ -95,7 +131,19 @@ class GroqHttpClient {
|
|
|
95
131
|
await this.handleErrorResponse(response);
|
|
96
132
|
}
|
|
97
133
|
|
|
98
|
-
|
|
134
|
+
const text = await response.text();
|
|
135
|
+
if (!text) {
|
|
136
|
+
throw new GroqError(GroqErrorType.SERVER_ERROR, "Empty response from server");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
return JSON.parse(text) as T;
|
|
141
|
+
} catch {
|
|
142
|
+
throw new GroqError(
|
|
143
|
+
GroqErrorType.SERVER_ERROR,
|
|
144
|
+
`Invalid JSON response: ${text.substring(0, 200)}`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
99
147
|
} catch (error) {
|
|
100
148
|
throw this.handleRequestError(error);
|
|
101
149
|
}
|
|
@@ -106,9 +154,17 @@ class GroqHttpClient {
|
|
|
106
154
|
const errorType = mapHttpStatusToErrorType(response.status);
|
|
107
155
|
|
|
108
156
|
try {
|
|
109
|
-
const
|
|
110
|
-
if (
|
|
111
|
-
|
|
157
|
+
const text = await response.text();
|
|
158
|
+
if (text) {
|
|
159
|
+
try {
|
|
160
|
+
const errorData = JSON.parse(text) as { error?: { message?: string } };
|
|
161
|
+
if (errorData.error?.message) {
|
|
162
|
+
errorMessage = errorData.error.message;
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
// Use text content if JSON parsing fails
|
|
166
|
+
errorMessage = text.substring(0, 500);
|
|
167
|
+
}
|
|
112
168
|
}
|
|
113
169
|
} catch {
|
|
114
170
|
// Use default message
|
|
@@ -122,9 +178,9 @@ class GroqHttpClient {
|
|
|
122
178
|
|
|
123
179
|
if (error instanceof Error) {
|
|
124
180
|
if (error.name === "AbortError") {
|
|
125
|
-
return new GroqError(GroqErrorType.ABORT_ERROR, "Request
|
|
181
|
+
return new GroqError(GroqErrorType.ABORT_ERROR, "Request timeout", error);
|
|
126
182
|
}
|
|
127
|
-
if (error.message.includes("network")) {
|
|
183
|
+
if (error.name === "TypeError" && error.message.includes("network")) {
|
|
128
184
|
return new GroqError(GroqErrorType.NETWORK_ERROR, "Network error", error);
|
|
129
185
|
}
|
|
130
186
|
}
|