@umituz/react-native-ai-groq-provider 1.0.21 → 1.0.22

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.
@@ -1,316 +0,0 @@
1
- /**
2
- * Chat Session Service
3
- * Manages multi-turn chat conversations with Groq API
4
- */
5
-
6
- import type {
7
- GroqMessage,
8
- GroqChatConfig,
9
- GroqGenerationConfig,
10
- } from "../../domain/entities";
11
- import { groqHttpClient } from "./GroqClient";
12
- import { DEFAULT_MODELS } from "../../domain/entities";
13
- import { GroqError, GroqErrorType } from "../../domain/entities/error.types";
14
- import { generateSessionId, calculateMaxMessages } from "../../infrastructure/utils/calculation.util";
15
-
16
- /**
17
- * Chat session state
18
- */
19
- export interface ChatSession {
20
- id: string;
21
- model: string;
22
- systemInstruction?: string;
23
- messages: GroqMessage[];
24
- generationConfig?: GroqGenerationConfig;
25
- createdAt: Date;
26
- updatedAt: Date;
27
- }
28
-
29
- /**
30
- * Result of sending a chat message
31
- */
32
- export interface ChatSendResult {
33
- response: string;
34
- usage: {
35
- promptTokens: number;
36
- completionTokens: number;
37
- totalTokens: number;
38
- };
39
- finishReason: string;
40
- }
41
-
42
- /**
43
- * Options for sending a chat message
44
- */
45
- export interface SendChatMessageOptions {
46
- /** Stream the response (not yet implemented) */
47
- stream?: boolean;
48
- }
49
-
50
- /**
51
- * Message format for chat history (simplified)
52
- */
53
- export type ChatHistoryMessage = {
54
- role: "system" | "user" | "assistant";
55
- content: string;
56
- };
57
-
58
- /**
59
- * Create a new chat session
60
- */
61
- export function createChatSession(config: GroqChatConfig = {}): ChatSession {
62
- return {
63
- id: generateSessionId("groq-chat"),
64
- model: config.model || DEFAULT_MODELS.TEXT,
65
- systemInstruction: config.systemInstruction,
66
- messages: config.history ? [...config.history] : [],
67
- generationConfig: config.generationConfig,
68
- createdAt: new Date(),
69
- updatedAt: new Date(),
70
- };
71
- }
72
-
73
- /**
74
- * Chat session service
75
- */
76
- class ChatSessionService {
77
- private sessions = new Map<string, ChatSession>();
78
- private readonly MAX_SESSIONS = 100; // Prevent unlimited memory growth
79
- private readonly SESSION_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
80
-
81
- /**
82
- * Create a new chat session
83
- */
84
- create(config: GroqChatConfig = {}): ChatSession {
85
- // Auto-cleanup old sessions before creating new one
86
- this.cleanupOldSessions();
87
-
88
- const session = createChatSession(config);
89
- this.sessions.set(session.id, session);
90
-
91
- // Enforce session limit
92
- if (this.sessions.size > this.MAX_SESSIONS) {
93
- // Remove oldest sessions
94
- const sortedSessions = Array.from(this.sessions.entries())
95
- .sort(([, a], [, b]) => a.createdAt.getTime() - b.createdAt.getTime());
96
-
97
- const toRemove = sortedSessions.slice(0, this.sessions.size - this.MAX_SESSIONS);
98
- for (const [id] of toRemove) {
99
- this.sessions.delete(id);
100
- }
101
- }
102
-
103
- return session;
104
- }
105
-
106
- /**
107
- * Get a session by ID
108
- */
109
- get(sessionId: string): ChatSession | undefined {
110
- return this.sessions.get(sessionId);
111
- }
112
-
113
- /**
114
- * Delete a session
115
- */
116
- delete(sessionId: string): boolean {
117
- return this.sessions.delete(sessionId);
118
- }
119
-
120
- /**
121
- * Cleanup old sessions automatically
122
- */
123
- private cleanupOldSessions(): void {
124
- const now = Date.now();
125
- const expiredIds: string[] = [];
126
-
127
- for (const [id, session] of this.sessions.entries()) {
128
- const age = now - session.updatedAt.getTime();
129
- if (age > this.SESSION_TTL_MS) {
130
- expiredIds.push(id);
131
- }
132
- }
133
-
134
- for (const id of expiredIds) {
135
- this.sessions.delete(id);
136
- }
137
- }
138
-
139
- /**
140
- * Get active session count
141
- */
142
- getActiveCount(): number {
143
- return this.sessions.size;
144
- }
145
-
146
- /**
147
- * Clear all sessions
148
- */
149
- clearAll(): void {
150
- this.sessions.clear();
151
- }
152
-
153
- /**
154
- * Send a message in a chat session
155
- */
156
- async send(
157
- sessionId: string,
158
- content: string,
159
- _options: SendChatMessageOptions = {}
160
- ): Promise<ChatSendResult> {
161
- const session = this.sessions.get(sessionId);
162
- if (!session) {
163
- throw new GroqError(
164
- GroqErrorType.MISSING_CONFIG,
165
- `Chat session ${sessionId} not found`
166
- );
167
- }
168
-
169
- // Add user message to history
170
- const userMessage: GroqMessage = {
171
- role: "user",
172
- content,
173
- };
174
- session.messages.push(userMessage);
175
-
176
- // Build messages array for API
177
- const messagesForApi = buildChatHistory(session);
178
-
179
- // Call API
180
- const response = await groqHttpClient.chatCompletion({
181
- model: session.model,
182
- messages: messagesForApi,
183
- temperature: session.generationConfig?.temperature || 0.7,
184
- max_tokens: session.generationConfig?.maxTokens || 1024,
185
- top_p: session.generationConfig?.topP,
186
- });
187
-
188
- // Extract assistant response
189
- const assistantContent = response.choices[0]?.message?.content;
190
- if (!assistantContent) {
191
- throw new GroqError(
192
- GroqErrorType.UNKNOWN_ERROR,
193
- "No content generated from Groq API"
194
- );
195
- }
196
-
197
- // Add assistant message to history
198
- const assistantMessage: GroqMessage = {
199
- role: "assistant",
200
- content: assistantContent,
201
- };
202
- session.messages.push(assistantMessage);
203
-
204
- // Update timestamp
205
- session.updatedAt = new Date();
206
-
207
- return {
208
- response: assistantContent,
209
- usage: {
210
- promptTokens: response.usage?.prompt_tokens || 0,
211
- completionTokens: response.usage?.completion_tokens || 0,
212
- totalTokens: response.usage?.total_tokens || 0,
213
- },
214
- finishReason: response.choices[0]?.finish_reason || "unknown",
215
- };
216
- }
217
-
218
- /**
219
- * Reset a chat session (clear messages except system instruction)
220
- */
221
- reset(sessionId: string): ChatSession {
222
- const session = this.sessions.get(sessionId);
223
- if (!session) {
224
- throw new GroqError(
225
- GroqErrorType.MISSING_CONFIG,
226
- `Chat session ${sessionId} not found`
227
- );
228
- }
229
-
230
- session.messages = [];
231
- session.updatedAt = new Date();
232
-
233
- return session;
234
- }
235
-
236
- /**
237
- * Update a chat session's config
238
- */
239
- updateConfig(sessionId: string, config: Partial<GroqChatConfig>): ChatSession {
240
- const session = this.sessions.get(sessionId);
241
- if (!session) {
242
- throw new GroqError(
243
- GroqErrorType.MISSING_CONFIG,
244
- `Chat session ${sessionId} not found`
245
- );
246
- }
247
-
248
- if (config.model !== undefined) session.model = config.model;
249
- if (config.systemInstruction !== undefined) session.systemInstruction = config.systemInstruction;
250
- if (config.generationConfig !== undefined) session.generationConfig = config.generationConfig;
251
- if (config.history) session.messages = [...config.history];
252
-
253
- session.updatedAt = new Date();
254
-
255
- return session;
256
- }
257
- }
258
-
259
- /**
260
- * Singleton instance
261
- */
262
- export const chatSessionService = new ChatSessionService();
263
-
264
- /**
265
- * Convenience function to send a chat message
266
- */
267
- export async function sendChatMessage(
268
- sessionId: string,
269
- content: string,
270
- options?: SendChatMessageOptions
271
- ): Promise<ChatSendResult> {
272
- return chatSessionService.send(sessionId, content, options);
273
- }
274
-
275
- /**
276
- * Build chat history for API request
277
- */
278
- export function buildChatHistory(session: ChatSession): GroqMessage[] {
279
- const messages: GroqMessage[] = [];
280
-
281
- // Add system instruction if present
282
- if (session.systemInstruction) {
283
- messages.push({
284
- role: "system",
285
- content: session.systemInstruction,
286
- });
287
- }
288
-
289
- // Add conversation history
290
- messages.push(...session.messages);
291
-
292
- return messages;
293
- }
294
-
295
- /**
296
- * Trim chat history to fit within token limit
297
- */
298
- export function trimChatHistory(
299
- messages: GroqMessage[],
300
- maxTokens: number = 4000
301
- ): GroqMessage[] {
302
- // Calculate max messages using utility function
303
- const maxMessages = calculateMaxMessages(maxTokens);
304
-
305
- if (messages.length <= maxMessages) {
306
- return messages;
307
- }
308
-
309
- // Keep system messages and trim from oldest to newest
310
- const systemMessages = messages.filter((m) => m.role === "system");
311
- const nonSystemMessages = messages.filter((m) => m.role !== "system");
312
-
313
- const trimmed = nonSystemMessages.slice(-maxMessages);
314
-
315
- return [...systemMessages, ...trimmed];
316
- }
@@ -1,351 +0,0 @@
1
- /**
2
- * Groq HTTP Client
3
- * Handles all HTTP communication with Groq API
4
- */
5
-
6
- import type {
7
- GroqConfig,
8
- GroqChatRequest,
9
- GroqChatResponse,
10
- GroqChatChunk,
11
- } from "../../domain/entities";
12
- import { GroqError, GroqErrorType, mapHttpStatusToErrorType } from "../../domain/entities/error.types";
13
- import { calculateSafeBufferSize } from "../../infrastructure/utils/calculation.util";
14
-
15
- const DEFAULT_BASE_URL = "https://api.groq.com/openai/v1";
16
- const DEFAULT_TIMEOUT = 60000; // 60 seconds
17
- const CHAT_COMPLETIONS_ENDPOINT = "/chat/completions";
18
-
19
- class GroqHttpClient {
20
- private config: GroqConfig | null = null;
21
- private initialized = false;
22
-
23
- /**
24
- * Initialize the client with configuration
25
- */
26
- initialize(config: GroqConfig): void {
27
- const apiKey = config.apiKey?.trim();
28
-
29
- if (typeof __DEV__ !== "undefined" && __DEV__) {
30
- console.log("[GroqClient] Initializing:", {
31
- hasApiKey: !!apiKey,
32
- keyLength: apiKey?.length,
33
- keyPrefix: apiKey ? apiKey.substring(0, 10) + "..." : "",
34
- baseUrl: config.baseUrl || DEFAULT_BASE_URL,
35
- timeoutMs: config.timeoutMs || DEFAULT_TIMEOUT,
36
- textModel: config.textModel,
37
- });
38
- }
39
-
40
- if (!apiKey || apiKey.length < 10) {
41
- throw new GroqError(
42
- GroqErrorType.INVALID_API_KEY,
43
- "API key is required and must be at least 10 characters"
44
- );
45
- }
46
-
47
- this.config = {
48
- apiKey,
49
- baseUrl: config.baseUrl || DEFAULT_BASE_URL,
50
- timeoutMs: config.timeoutMs || DEFAULT_TIMEOUT,
51
- textModel: config.textModel,
52
- };
53
- this.initialized = true;
54
-
55
- if (typeof __DEV__ !== "undefined" && __DEV__) {
56
- console.log("[GroqClient] Initialization complete:", {
57
- initialized: this.initialized,
58
- baseUrl: this.config.baseUrl,
59
- });
60
- }
61
- }
62
-
63
- /**
64
- * Check if client is initialized
65
- */
66
- isInitialized(): boolean {
67
- return this.initialized && this.config !== null;
68
- }
69
-
70
- /**
71
- * Get current configuration
72
- */
73
- getConfig(): GroqConfig {
74
- if (!this.config || !this.initialized) {
75
- throw new GroqError(
76
- GroqErrorType.MISSING_CONFIG,
77
- "Client not initialized. Call initialize() first."
78
- );
79
- }
80
- return this.config;
81
- }
82
-
83
- /**
84
- * Make an HTTP request to Groq API
85
- */
86
- private async request<T>(
87
- endpoint: string,
88
- body: unknown,
89
- signal?: AbortSignal
90
- ): Promise<T> {
91
- if (!this.config || !this.initialized) {
92
- throw new GroqError(
93
- GroqErrorType.MISSING_CONFIG,
94
- "Client not initialized. Call initialize() first."
95
- );
96
- }
97
-
98
- const url = `${this.config.baseUrl}${endpoint}`;
99
- const timeout = this.config.timeoutMs || DEFAULT_TIMEOUT;
100
-
101
- if (typeof __DEV__ !== "undefined" && __DEV__) {
102
- console.log("[GroqClient] API Request:", {
103
- url,
104
- endpoint,
105
- method: "POST",
106
- timeout: `${timeout}ms`,
107
- hasBody: !!body,
108
- bodyKeys: body ? Object.keys(body) : [],
109
- });
110
- }
111
-
112
- try {
113
- // Create AbortController for timeout
114
- const controller = new AbortController();
115
- const timeoutId = setTimeout(() => controller.abort(), timeout);
116
-
117
- // Use provided signal if available
118
- if (signal) {
119
- signal.addEventListener("abort", () => controller.abort());
120
- }
121
-
122
- const fetchStartTime = Date.now();
123
- const response = await fetch(url, {
124
- method: "POST",
125
- headers: {
126
- "Content-Type": "application/json",
127
- "Authorization": `Bearer ${this.config.apiKey}`,
128
- },
129
- body: JSON.stringify(body),
130
- signal: controller.signal,
131
- });
132
-
133
- const fetchDuration = Date.now() - fetchStartTime;
134
- clearTimeout(timeoutId);
135
-
136
- if (typeof __DEV__ !== "undefined" && __DEV__) {
137
- console.log("[GroqClient] API Response:", {
138
- status: response.status,
139
- ok: response.ok,
140
- fetchDuration: `${fetchDuration}ms`,
141
- contentType: response.headers.get("content-type"),
142
- });
143
- }
144
-
145
- if (!response.ok) {
146
- await this.handleErrorResponse(response);
147
- }
148
-
149
- const jsonStart = Date.now();
150
- const jsonData = (await response.json()) as T;
151
- const parseDuration = Date.now() - jsonStart;
152
-
153
- if (typeof __DEV__ !== "undefined" && __DEV__) {
154
- console.log("[GroqClient] JSON parsed:", {
155
- parseDuration: `${parseDuration}ms`,
156
- hasData: !!jsonData,
157
- dataKeys: jsonData ? Object.keys(jsonData) : [],
158
- });
159
- }
160
-
161
- return jsonData;
162
- } catch (error) {
163
- if (typeof __DEV__ !== "undefined" && __DEV__) {
164
- console.error("[GroqClient] Request error:", {
165
- error,
166
- errorMessage: error instanceof Error ? error.message : String(error),
167
- errorName: error instanceof Error ? error.name : undefined,
168
- });
169
- }
170
-
171
- throw this.handleRequestError(error);
172
- }
173
- }
174
-
175
- /**
176
- * Handle HTTP error responses
177
- */
178
- private async handleErrorResponse(response: Response): Promise<never> {
179
- let errorMessage = `HTTP ${response.status}`;
180
- let errorType = mapHttpStatusToErrorType(response.status);
181
-
182
- try {
183
- const errorData = (await response.json()) as { error?: { message?: string } };
184
- if (errorData.error?.message) {
185
- errorMessage = errorData.error.message;
186
- }
187
- } catch {
188
- // If parsing fails, use default message
189
- }
190
-
191
- throw new GroqError(errorType, errorMessage, {
192
- status: response.status,
193
- url: response.url,
194
- });
195
- }
196
-
197
- /**
198
- * Handle request errors (network, timeout, abort)
199
- */
200
- private handleRequestError(error: unknown): GroqError {
201
- if (error instanceof GroqError) {
202
- return error;
203
- }
204
-
205
- if (error instanceof Error) {
206
- if (error.name === "AbortError") {
207
- return new GroqError(
208
- GroqErrorType.ABORT_ERROR,
209
- "Request was aborted by the client",
210
- error
211
- );
212
- }
213
-
214
- if (error.name === "TypeError" && error.message.includes("network")) {
215
- return new GroqError(
216
- GroqErrorType.NETWORK_ERROR,
217
- "Network error: Unable to connect to Groq API",
218
- error
219
- );
220
- }
221
- }
222
-
223
- return new GroqError(
224
- GroqErrorType.UNKNOWN_ERROR,
225
- error instanceof Error ? error.message : "Unknown error occurred",
226
- error
227
- );
228
- }
229
-
230
- /**
231
- * Send chat completion request (non-streaming)
232
- */
233
- async chatCompletion(
234
- request: GroqChatRequest,
235
- signal?: AbortSignal
236
- ): Promise<GroqChatResponse> {
237
- return this.request<GroqChatResponse>(
238
- CHAT_COMPLETIONS_ENDPOINT,
239
- { ...request, stream: false },
240
- signal
241
- );
242
- }
243
-
244
- /**
245
- * Send chat completion request (streaming)
246
- * Returns an async generator of chunks
247
- */
248
- async *chatCompletionStream(
249
- request: GroqChatRequest,
250
- signal?: AbortSignal
251
- ): AsyncGenerator<GroqChatChunk> {
252
- if (!this.config || !this.initialized) {
253
- throw new GroqError(
254
- GroqErrorType.MISSING_CONFIG,
255
- "Client not initialized. Call initialize() first."
256
- );
257
- }
258
-
259
- const url = `${this.config.baseUrl}${CHAT_COMPLETIONS_ENDPOINT}`;
260
- const timeout = this.config.timeoutMs || DEFAULT_TIMEOUT;
261
-
262
- // Create AbortController for timeout
263
- const controller = new AbortController();
264
- const timeoutId = setTimeout(() => controller.abort(), timeout);
265
-
266
- // Use provided signal if available
267
- if (signal) {
268
- signal.addEventListener("abort", () => controller.abort());
269
- }
270
-
271
- try {
272
- const response = await fetch(url, {
273
- method: "POST",
274
- headers: {
275
- "Content-Type": "application/json",
276
- "Authorization": `Bearer ${this.config.apiKey}`,
277
- },
278
- body: JSON.stringify({ ...request, stream: true }),
279
- signal: controller.signal,
280
- });
281
-
282
- clearTimeout(timeoutId);
283
-
284
- if (!response.ok) {
285
- await this.handleErrorResponse(response);
286
- }
287
-
288
- if (!response.body) {
289
- throw new GroqError(
290
- GroqErrorType.NETWORK_ERROR,
291
- "Response body is null"
292
- );
293
- }
294
-
295
- // Read and parse Server-Sent Events
296
- const reader = response.body.getReader();
297
- const decoder = new TextDecoder();
298
- let buffer = "";
299
- const MAX_BUFFER_SIZE = 1024 * 1024; // 1MB max buffer to prevent memory issues
300
-
301
- while (true) {
302
- const { done, value } = await reader.read();
303
-
304
- if (done) break;
305
-
306
- buffer += decoder.decode(value, { stream: true });
307
-
308
- // Prevent unlimited buffer growth using utility function
309
- const safeSize = calculateSafeBufferSize(buffer.length, MAX_BUFFER_SIZE);
310
- if (safeSize < buffer.length) {
311
- buffer = buffer.slice(-safeSize);
312
- }
313
-
314
- const lines = buffer.split("\n");
315
- buffer = lines.pop() || "";
316
-
317
- for (const line of lines) {
318
- const trimmed = line.trim();
319
- if (!trimmed || trimmed === "data: [DONE]") continue;
320
-
321
- if (trimmed.startsWith("data: ")) {
322
- const jsonStr = trimmed.slice(6);
323
- try {
324
- const chunk = JSON.parse(jsonStr) as GroqChatChunk;
325
- yield chunk;
326
- } catch (error) {
327
- if (typeof __DEV__ !== "undefined" && __DEV__) {
328
- console.error("Failed to parse SSE chunk:", error);
329
- }
330
- }
331
- }
332
- }
333
- }
334
- } catch (error) {
335
- throw this.handleRequestError(error);
336
- }
337
- }
338
-
339
- /**
340
- * Reset the client
341
- */
342
- reset(): void {
343
- this.config = null;
344
- this.initialized = false;
345
- }
346
- }
347
-
348
- /**
349
- * Singleton instance
350
- */
351
- export const groqHttpClient = new GroqHttpClient();