@umituz/react-native-ai-groq-provider 1.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.
@@ -0,0 +1,280 @@
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
+
14
+ const DEFAULT_BASE_URL = "https://api.groq.com/openai/v1";
15
+ const DEFAULT_TIMEOUT = 60000; // 60 seconds
16
+ const CHAT_COMPLETIONS_ENDPOINT = "/chat/completions";
17
+
18
+ class GroqHttpClient {
19
+ private config: GroqConfig | null = null;
20
+ private initialized = false;
21
+
22
+ /**
23
+ * Initialize the client with configuration
24
+ */
25
+ initialize(config: GroqConfig): void {
26
+ const apiKey = config.apiKey?.trim();
27
+
28
+ if (!apiKey || apiKey.length < 10) {
29
+ throw new GroqError(
30
+ GroqErrorType.INVALID_API_KEY,
31
+ "API key is required and must be at least 10 characters"
32
+ );
33
+ }
34
+
35
+ this.config = {
36
+ apiKey,
37
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL,
38
+ timeoutMs: config.timeoutMs || DEFAULT_TIMEOUT,
39
+ textModel: config.textModel,
40
+ };
41
+ this.initialized = true;
42
+ }
43
+
44
+ /**
45
+ * Check if client is initialized
46
+ */
47
+ isInitialized(): boolean {
48
+ return this.initialized && this.config !== null;
49
+ }
50
+
51
+ /**
52
+ * Get current configuration
53
+ */
54
+ getConfig(): GroqConfig {
55
+ if (!this.config || !this.initialized) {
56
+ throw new GroqError(
57
+ GroqErrorType.MISSING_CONFIG,
58
+ "Client not initialized. Call initialize() first."
59
+ );
60
+ }
61
+ return this.config;
62
+ }
63
+
64
+ /**
65
+ * Make an HTTP request to Groq API
66
+ */
67
+ private async request<T>(
68
+ endpoint: string,
69
+ body: unknown,
70
+ signal?: AbortSignal
71
+ ): Promise<T> {
72
+ if (!this.config || !this.initialized) {
73
+ throw new GroqError(
74
+ GroqErrorType.MISSING_CONFIG,
75
+ "Client not initialized. Call initialize() first."
76
+ );
77
+ }
78
+
79
+ const url = `${this.config.baseUrl}${endpoint}`;
80
+ const timeout = this.config.timeoutMs || DEFAULT_TIMEOUT;
81
+
82
+ try {
83
+ // Create AbortController for timeout
84
+ const controller = new AbortController();
85
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
86
+
87
+ // Use provided signal if available
88
+ if (signal) {
89
+ signal.addEventListener("abort", () => controller.abort());
90
+ }
91
+
92
+ const response = await fetch(url, {
93
+ method: "POST",
94
+ headers: {
95
+ "Content-Type": "application/json",
96
+ "Authorization": `Bearer ${this.config.apiKey}`,
97
+ },
98
+ body: JSON.stringify(body),
99
+ signal: controller.signal,
100
+ });
101
+
102
+ clearTimeout(timeoutId);
103
+
104
+ if (!response.ok) {
105
+ await this.handleErrorResponse(response);
106
+ }
107
+
108
+ return (await response.json()) as T;
109
+ } catch (error) {
110
+ throw this.handleRequestError(error);
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Handle HTTP error responses
116
+ */
117
+ private async handleErrorResponse(response: Response): Promise<never> {
118
+ let errorMessage = `HTTP ${response.status}`;
119
+ let errorType = mapHttpStatusToErrorType(response.status);
120
+
121
+ try {
122
+ const errorData = (await response.json()) as { error?: { message?: string } };
123
+ if (errorData.error?.message) {
124
+ errorMessage = errorData.error.message;
125
+ }
126
+ } catch {
127
+ // If parsing fails, use default message
128
+ }
129
+
130
+ throw new GroqError(errorType, errorMessage, {
131
+ status: response.status,
132
+ url: response.url,
133
+ });
134
+ }
135
+
136
+ /**
137
+ * Handle request errors (network, timeout, abort)
138
+ */
139
+ private handleRequestError(error: unknown): GroqError {
140
+ if (error instanceof GroqError) {
141
+ return error;
142
+ }
143
+
144
+ if (error instanceof Error) {
145
+ if (error.name === "AbortError") {
146
+ return new GroqError(
147
+ GroqErrorType.ABORT_ERROR,
148
+ "Request was aborted by the client",
149
+ error
150
+ );
151
+ }
152
+
153
+ if (error.name === "TypeError" && error.message.includes("network")) {
154
+ return new GroqError(
155
+ GroqErrorType.NETWORK_ERROR,
156
+ "Network error: Unable to connect to Groq API",
157
+ error
158
+ );
159
+ }
160
+ }
161
+
162
+ return new GroqError(
163
+ GroqErrorType.UNKNOWN_ERROR,
164
+ error instanceof Error ? error.message : "Unknown error occurred",
165
+ error
166
+ );
167
+ }
168
+
169
+ /**
170
+ * Send chat completion request (non-streaming)
171
+ */
172
+ async chatCompletion(
173
+ request: GroqChatRequest,
174
+ signal?: AbortSignal
175
+ ): Promise<GroqChatResponse> {
176
+ return this.request<GroqChatResponse>(
177
+ CHAT_COMPLETIONS_ENDPOINT,
178
+ { ...request, stream: false },
179
+ signal
180
+ );
181
+ }
182
+
183
+ /**
184
+ * Send chat completion request (streaming)
185
+ * Returns an async generator of chunks
186
+ */
187
+ async *chatCompletionStream(
188
+ request: GroqChatRequest,
189
+ signal?: AbortSignal
190
+ ): AsyncGenerator<GroqChatChunk> {
191
+ if (!this.config || !this.initialized) {
192
+ throw new GroqError(
193
+ GroqErrorType.MISSING_CONFIG,
194
+ "Client not initialized. Call initialize() first."
195
+ );
196
+ }
197
+
198
+ const url = `${this.config.baseUrl}${CHAT_COMPLETIONS_ENDPOINT}`;
199
+ const timeout = this.config.timeoutMs || DEFAULT_TIMEOUT;
200
+
201
+ // Create AbortController for timeout
202
+ const controller = new AbortController();
203
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
204
+
205
+ // Use provided signal if available
206
+ if (signal) {
207
+ signal.addEventListener("abort", () => controller.abort());
208
+ }
209
+
210
+ try {
211
+ const response = await fetch(url, {
212
+ method: "POST",
213
+ headers: {
214
+ "Content-Type": "application/json",
215
+ "Authorization": `Bearer ${this.config.apiKey}`,
216
+ },
217
+ body: JSON.stringify({ ...request, stream: true }),
218
+ signal: controller.signal,
219
+ });
220
+
221
+ clearTimeout(timeoutId);
222
+
223
+ if (!response.ok) {
224
+ await this.handleErrorResponse(response);
225
+ }
226
+
227
+ if (!response.body) {
228
+ throw new GroqError(
229
+ GroqErrorType.NETWORK_ERROR,
230
+ "Response body is null"
231
+ );
232
+ }
233
+
234
+ // Read and parse Server-Sent Events
235
+ const reader = response.body.getReader();
236
+ const decoder = new TextDecoder();
237
+ let buffer = "";
238
+
239
+ while (true) {
240
+ const { done, value } = await reader.read();
241
+
242
+ if (done) break;
243
+
244
+ buffer += decoder.decode(value, { stream: true });
245
+ const lines = buffer.split("\n");
246
+ buffer = lines.pop() || "";
247
+
248
+ for (const line of lines) {
249
+ const trimmed = line.trim();
250
+ if (!trimmed || trimmed === "data: [DONE]") continue;
251
+
252
+ if (trimmed.startsWith("data: ")) {
253
+ const jsonStr = trimmed.slice(6);
254
+ try {
255
+ const chunk = JSON.parse(jsonStr) as GroqChatChunk;
256
+ yield chunk;
257
+ } catch (error) {
258
+ console.error("Failed to parse SSE chunk:", error);
259
+ }
260
+ }
261
+ }
262
+ }
263
+ } catch (error) {
264
+ throw this.handleRequestError(error);
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Reset the client
270
+ */
271
+ reset(): void {
272
+ this.config = null;
273
+ this.initialized = false;
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Singleton instance
279
+ */
280
+ export const groqHttpClient = new GroqHttpClient();
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Streaming Service
3
+ * Handles streaming responses from Groq API
4
+ */
5
+
6
+ import type {
7
+ GroqChatRequest,
8
+ GroqMessage,
9
+ GroqGenerationConfig,
10
+ } from "../../domain/entities";
11
+ import { groqHttpClient } from "./GroqClient";
12
+ import { DEFAULT_MODELS } from "../../domain/entities";
13
+
14
+ export interface StreamingCallbacks {
15
+ /** Called when new content chunk is received */
16
+ onChunk?: (chunk: string) => void;
17
+ /** Called when streaming completes */
18
+ onComplete?: (fullContent: string) => void;
19
+ /** Called when an error occurs */
20
+ onError?: (error: Error) => void;
21
+ }
22
+
23
+ export interface StreamingOptions {
24
+ model?: string;
25
+ generationConfig?: GroqGenerationConfig;
26
+ callbacks?: StreamingCallbacks;
27
+ }
28
+
29
+ /**
30
+ * Stream text generation from a prompt
31
+ */
32
+ export async function* streaming(
33
+ prompt: string,
34
+ options: StreamingOptions = {}
35
+ ): AsyncGenerator<string> {
36
+ const model = options.model || DEFAULT_MODELS.TEXT;
37
+
38
+ const messages: GroqMessage[] = [
39
+ {
40
+ role: "user",
41
+ content: prompt,
42
+ },
43
+ ];
44
+
45
+ const request: GroqChatRequest = {
46
+ model,
47
+ messages,
48
+ temperature: options.generationConfig?.temperature || 0.7,
49
+ max_tokens: options.generationConfig?.maxTokens || 1024,
50
+ top_p: options.generationConfig?.topP,
51
+ };
52
+
53
+ try {
54
+ for await (const chunk of groqHttpClient.chatCompletionStream(request)) {
55
+ const content = chunk.choices[0]?.delta?.content;
56
+ if (content) {
57
+ options.callbacks?.onChunk?.(content);
58
+ yield content;
59
+ }
60
+ }
61
+ options.callbacks?.onComplete?.(await collectStreamContent(request));
62
+ } catch (error) {
63
+ options.callbacks?.onError?.(error as Error);
64
+ throw error;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Stream chat generation from messages
70
+ */
71
+ export async function* streamingChat(
72
+ messages: GroqMessage[],
73
+ options: StreamingOptions = {}
74
+ ): AsyncGenerator<string> {
75
+ const model = options.model || DEFAULT_MODELS.TEXT;
76
+
77
+ const request: GroqChatRequest = {
78
+ model,
79
+ messages,
80
+ temperature: options.generationConfig?.temperature || 0.7,
81
+ max_tokens: options.generationConfig?.maxTokens || 1024,
82
+ top_p: options.generationConfig?.topP,
83
+ };
84
+
85
+ try {
86
+ for await (const chunk of groqHttpClient.chatCompletionStream(request)) {
87
+ const content = chunk.choices[0]?.delta?.content;
88
+ if (content) {
89
+ options.callbacks?.onChunk?.(content);
90
+ yield content;
91
+ }
92
+ }
93
+ options.callbacks?.onComplete?.(await collectStreamContent(request));
94
+ } catch (error) {
95
+ options.callbacks?.onError?.(error as Error);
96
+ throw error;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Collect full content from streaming (for onComplete callback)
102
+ */
103
+ async function collectStreamContent(request: GroqChatRequest): Promise<string> {
104
+ let fullContent = "";
105
+
106
+ for await (const chunk of groqHttpClient.chatCompletionStream(request)) {
107
+ const content = chunk.choices[0]?.delta?.content;
108
+ if (content) {
109
+ fullContent += content;
110
+ }
111
+ }
112
+
113
+ return fullContent;
114
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Structured Text Generation Service
3
+ * Generates structured JSON output from Groq API
4
+ */
5
+
6
+ import type {
7
+ GroqChatRequest,
8
+ GroqMessage,
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
+
15
+ export interface StructuredTextOptions<T> {
16
+ model?: string;
17
+ generationConfig?: GroqGenerationConfig;
18
+ schema?: Record<string, unknown>;
19
+ example?: T;
20
+ }
21
+
22
+ /**
23
+ * Generate structured JSON output from a prompt
24
+ */
25
+ export async function structuredText<T = Record<string, unknown>>(
26
+ prompt: string,
27
+ options: StructuredTextOptions<T> = {}
28
+ ): Promise<T> {
29
+ const model = options.model || DEFAULT_MODELS.TEXT;
30
+
31
+ let systemPrompt = "You are a helpful assistant that generates valid JSON output.";
32
+
33
+ if (options.schema) {
34
+ systemPrompt += `\n\nResponse must conform to this JSON schema:\n${JSON.stringify(options.schema, null, 2)}`;
35
+ }
36
+
37
+ if (options.example) {
38
+ systemPrompt += `\n\nExample response format:\n${JSON.stringify(options.example, null, 2)}`;
39
+ }
40
+
41
+ systemPrompt += "\n\nIMPORTANT: Respond ONLY with valid JSON. No markdown, no code blocks, no explanations.";
42
+
43
+ const messages: GroqMessage[] = [
44
+ {
45
+ role: "system",
46
+ content: systemPrompt,
47
+ },
48
+ {
49
+ role: "user",
50
+ content: prompt,
51
+ },
52
+ ];
53
+
54
+ const request: GroqChatRequest = {
55
+ model,
56
+ messages,
57
+ temperature: options.generationConfig?.temperature || 0.3,
58
+ max_tokens: options.generationConfig?.maxTokens || 2048,
59
+ top_p: options.generationConfig?.topP || 0.9,
60
+ };
61
+
62
+ const response = await groqHttpClient.chatCompletion(request);
63
+
64
+ let content = response.choices[0]?.message?.content;
65
+ if (!content) {
66
+ throw new GroqError(
67
+ GroqErrorType.UNKNOWN_ERROR,
68
+ "No content generated from Groq API"
69
+ );
70
+ }
71
+
72
+ // Clean up the response: remove markdown code blocks if present
73
+ content = content.trim();
74
+ if (content.startsWith("```json")) {
75
+ content = content.slice(7);
76
+ } else if (content.startsWith("```")) {
77
+ content = content.slice(3);
78
+ }
79
+ if (content.endsWith("```")) {
80
+ content = content.slice(0, -3);
81
+ }
82
+ content = content.trim();
83
+
84
+ try {
85
+ return JSON.parse(content) as T;
86
+ } catch (error) {
87
+ throw new GroqError(
88
+ GroqErrorType.UNKNOWN_ERROR,
89
+ `Failed to parse JSON response: ${content}`,
90
+ error
91
+ );
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Generate structured JSON output from messages
97
+ */
98
+ export async function structuredChat<T = Record<string, unknown>>(
99
+ messages: GroqMessage[],
100
+ options: StructuredTextOptions<T> = {}
101
+ ): Promise<T> {
102
+ const model = options.model || DEFAULT_MODELS.TEXT;
103
+
104
+ let systemMessage: GroqMessage | null = null;
105
+
106
+ // Check if there's already a system message
107
+ const hasSystemMessage = messages.some((m) => m.role === "system");
108
+
109
+ if (!hasSystemMessage) {
110
+ let systemPrompt = "You are a helpful assistant that generates valid JSON output.";
111
+
112
+ if (options.schema) {
113
+ systemPrompt += `\n\nResponse must conform to this JSON schema:\n${JSON.stringify(options.schema, null, 2)}`;
114
+ }
115
+
116
+ if (options.example) {
117
+ systemPrompt += `\n\nExample response format:\n${JSON.stringify(options.example, null, 2)}`;
118
+ }
119
+
120
+ systemPrompt += "\n\nIMPORTANT: Respond ONLY with valid JSON. No markdown, no code blocks, no explanations.";
121
+
122
+ systemMessage = {
123
+ role: "system",
124
+ content: systemPrompt,
125
+ };
126
+ }
127
+
128
+ const requestMessages = systemMessage
129
+ ? [systemMessage, ...messages]
130
+ : messages;
131
+
132
+ const request: GroqChatRequest = {
133
+ model,
134
+ messages: requestMessages,
135
+ temperature: options.generationConfig?.temperature || 0.3,
136
+ max_tokens: options.generationConfig?.maxTokens || 2048,
137
+ top_p: options.generationConfig?.topP || 0.9,
138
+ };
139
+
140
+ const response = await groqHttpClient.chatCompletion(request);
141
+
142
+ let content = response.choices[0]?.message?.content;
143
+ if (!content) {
144
+ throw new GroqError(
145
+ GroqErrorType.UNKNOWN_ERROR,
146
+ "No content generated from Groq API"
147
+ );
148
+ }
149
+
150
+ // Clean up the response: remove markdown code blocks if present
151
+ content = content.trim();
152
+ if (content.startsWith("```json")) {
153
+ content = content.slice(7);
154
+ } else if (content.startsWith("```")) {
155
+ content = content.slice(3);
156
+ }
157
+ if (content.endsWith("```")) {
158
+ content = content.slice(0, -3);
159
+ }
160
+ content = content.trim();
161
+
162
+ try {
163
+ return JSON.parse(content) as T;
164
+ } catch (error) {
165
+ throw new GroqError(
166
+ GroqErrorType.UNKNOWN_ERROR,
167
+ `Failed to parse JSON response: ${content}`,
168
+ error
169
+ );
170
+ }
171
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Text Generation Service
3
+ * Handles basic text generation using Groq API
4
+ */
5
+
6
+ import type {
7
+ GroqChatRequest,
8
+ GroqChatResponse,
9
+ GroqMessage,
10
+ GroqGenerationConfig,
11
+ } from "../../domain/entities";
12
+ import { groqHttpClient } from "./GroqClient";
13
+ import { DEFAULT_MODELS } from "../../domain/entities";
14
+ import { GroqError, GroqErrorType } from "../../domain/entities/error.types";
15
+
16
+ export interface TextGenerationOptions {
17
+ model?: string;
18
+ generationConfig?: GroqGenerationConfig;
19
+ }
20
+
21
+ /**
22
+ * Generate text from a simple prompt
23
+ */
24
+ export async function textGeneration(
25
+ prompt: string,
26
+ options: TextGenerationOptions = {}
27
+ ): Promise<string> {
28
+ const model = options.model || DEFAULT_MODELS.TEXT;
29
+
30
+ const messages: GroqMessage[] = [
31
+ {
32
+ role: "user",
33
+ content: prompt,
34
+ },
35
+ ];
36
+
37
+ const request: GroqChatRequest = {
38
+ model,
39
+ messages,
40
+ temperature: options.generationConfig?.temperature || 0.7,
41
+ max_tokens: options.generationConfig?.maxTokens || 1024,
42
+ top_p: options.generationConfig?.topP,
43
+ n: options.generationConfig?.n,
44
+ stop: options.generationConfig?.stop,
45
+ frequency_penalty: options.generationConfig?.frequencyPenalty,
46
+ presence_penalty: options.generationConfig?.presencePenalty,
47
+ };
48
+
49
+ const response = await groqHttpClient.chatCompletion(request);
50
+
51
+ const content = response.choices[0]?.message?.content;
52
+ if (!content) {
53
+ throw new GroqError(
54
+ GroqErrorType.UNKNOWN_ERROR,
55
+ "No content generated from Groq API"
56
+ );
57
+ }
58
+
59
+ return content;
60
+ }
61
+
62
+ /**
63
+ * Generate text from an array of messages
64
+ */
65
+ export async function chatGeneration(
66
+ messages: GroqMessage[],
67
+ options: TextGenerationOptions = {}
68
+ ): Promise<string> {
69
+ const model = options.model || DEFAULT_MODELS.TEXT;
70
+
71
+ const request: GroqChatRequest = {
72
+ model,
73
+ messages,
74
+ temperature: options.generationConfig?.temperature || 0.7,
75
+ max_tokens: options.generationConfig?.maxTokens || 1024,
76
+ top_p: options.generationConfig?.topP,
77
+ n: options.generationConfig?.n,
78
+ stop: options.generationConfig?.stop,
79
+ frequency_penalty: options.generationConfig?.frequencyPenalty,
80
+ presence_penalty: options.generationConfig?.presencePenalty,
81
+ };
82
+
83
+ const response = await groqHttpClient.chatCompletion(request);
84
+
85
+ const content = response.choices[0]?.message?.content;
86
+ if (!content) {
87
+ throw new GroqError(
88
+ GroqErrorType.UNKNOWN_ERROR,
89
+ "No content generated from Groq API"
90
+ );
91
+ }
92
+
93
+ return content;
94
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Infrastructure Services
3
+ */
4
+
5
+ export { groqHttpClient } from "./GroqClient";
6
+ export { textGeneration, chatGeneration } from "./TextGeneration";
7
+ export { structuredText, structuredChat } from "./StructuredText";
8
+ export { streaming, streamingChat, type StreamingCallbacks, type StreamingOptions } from "./Streaming";
9
+ export {
10
+ chatSessionService,
11
+ createChatSession,
12
+ sendChatMessage,
13
+ buildChatHistory,
14
+ trimChatHistory,
15
+ type ChatSession,
16
+ type SendChatMessageOptions,
17
+ type ChatSendResult,
18
+ type ChatHistoryMessage,
19
+ } from "./ChatSession";