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

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-groq-provider",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "description": "Groq text generation provider for React Native applications",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./dist/index.d.ts",
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Chat Session Use Case
3
+ * Manages multi-turn chat conversations
4
+ */
5
+
6
+ import type { GroqMessage, GroqChatConfig } from "../../domain/entities";
7
+ import { groqHttpClient } from "../../infrastructure/http";
8
+ import { RequestBuilder } from "../../shared/request-builder";
9
+ import { ResponseHandler } from "../../shared/response-handler";
10
+ import { logger } from "../../shared/logger";
11
+ import { GroqError, GroqErrorType } from "../../domain/entities/error.types";
12
+ import { generateSessionId } from "../../utils/calculation.util";
13
+
14
+ export interface ChatSession {
15
+ id: string;
16
+ model: string;
17
+ systemInstruction?: string;
18
+ messages: GroqMessage[];
19
+ createdAt: Date;
20
+ updatedAt: Date;
21
+ }
22
+
23
+ export interface ChatSendResult {
24
+ response: string;
25
+ usage: {
26
+ promptTokens: number;
27
+ completionTokens: number;
28
+ totalTokens: number;
29
+ };
30
+ finishReason: string;
31
+ }
32
+
33
+ class ChatSessionManager {
34
+ private sessions = new Map<string, ChatSession>();
35
+ private readonly MAX_SESSIONS = 100;
36
+ private readonly SESSION_TTL_MS = 24 * 60 * 60 * 1000;
37
+
38
+ create(config: GroqChatConfig = {}): ChatSession {
39
+ this.cleanupOldSessions();
40
+
41
+ const session: ChatSession = {
42
+ id: generateSessionId("groq-chat"),
43
+ model: config.model || "llama-3.3-70b-versatile",
44
+ systemInstruction: config.systemInstruction,
45
+ messages: config.history ? [...config.history] : [],
46
+ createdAt: new Date(),
47
+ updatedAt: new Date(),
48
+ };
49
+
50
+ this.sessions.set(session.id, session);
51
+
52
+ if (this.sessions.size > this.MAX_SESSIONS) {
53
+ const sorted = Array.from(this.sessions.entries())
54
+ .sort(([, a], [, b]) => a.createdAt.getTime() - b.createdAt.getTime());
55
+
56
+ const toRemove = sorted.slice(0, this.sessions.size - this.MAX_SESSIONS);
57
+ toRemove.forEach(([id]) => this.sessions.delete(id));
58
+ }
59
+
60
+ return session;
61
+ }
62
+
63
+ get(sessionId: string): ChatSession | undefined {
64
+ return this.sessions.get(sessionId);
65
+ }
66
+
67
+ delete(sessionId: string): boolean {
68
+ return this.sessions.delete(sessionId);
69
+ }
70
+
71
+ private cleanupOldSessions(): void {
72
+ const now = Date.now();
73
+ const expiredIds: string[] = [];
74
+
75
+ for (const [id, session] of this.sessions.entries()) {
76
+ const age = now - session.updatedAt.getTime();
77
+ if (age > this.SESSION_TTL_MS) {
78
+ expiredIds.push(id);
79
+ }
80
+ }
81
+
82
+ expiredIds.forEach((id) => this.sessions.delete(id));
83
+ }
84
+
85
+ async send(sessionId: string, content: string): Promise<ChatSendResult> {
86
+ const session = this.sessions.get(sessionId);
87
+ if (!session) {
88
+ throw new GroqError(
89
+ GroqErrorType.MISSING_CONFIG,
90
+ `Chat session ${sessionId} not found`
91
+ );
92
+ }
93
+
94
+ const userMessage: GroqMessage = { role: "user", content };
95
+ session.messages.push(userMessage);
96
+
97
+ const messages = this.buildMessages(session);
98
+ const request = RequestBuilder.buildChatRequest(messages, {
99
+ model: session.model,
100
+ });
101
+
102
+ logger.debug("ChatSession", "Sending message", {
103
+ sessionId,
104
+ messageCount: session.messages.length,
105
+ });
106
+
107
+ const response = await groqHttpClient.chatCompletion(request);
108
+ const handled = ResponseHandler.handleResponse(response);
109
+
110
+ const assistantMessage: GroqMessage = {
111
+ role: "assistant",
112
+ content: handled.content,
113
+ };
114
+ session.messages.push(assistantMessage);
115
+ session.updatedAt = new Date();
116
+
117
+ return {
118
+ response: handled.content,
119
+ usage: handled.usage,
120
+ finishReason: handled.finishReason,
121
+ };
122
+ }
123
+
124
+ private buildMessages(session: ChatSession): GroqMessage[] {
125
+ const messages: GroqMessage[] = [];
126
+
127
+ if (session.systemInstruction) {
128
+ messages.push({ role: "system", content: session.systemInstruction });
129
+ }
130
+
131
+ messages.push(...session.messages);
132
+
133
+ return messages;
134
+ }
135
+ }
136
+
137
+ export const chatSessionManager = new ChatSessionManager();
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Application Layer - Use Cases
3
+ * Business logic orchestrators
4
+ */
5
+
6
+ export { generateText } from "./text-generation.usecase";
7
+ export type { TextGenerationOptions } from "./text-generation.usecase";
8
+
9
+ export { generateStructured } from "./structured-generation.usecase";
10
+ export type { StructuredGenerationOptions } from "./structured-generation.usecase";
11
+
12
+ export { streamText } from "./streaming.usecase";
13
+ export type { StreamingCallbacks, StreamingOptions } from "./streaming.usecase";
14
+
15
+ export {
16
+ chatSessionManager,
17
+ type ChatSession,
18
+ type ChatSendResult,
19
+ } from "./chat-session.usecase";
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Streaming Use Case
3
+ * Handles streaming text generation
4
+ */
5
+
6
+ import type { GroqGenerationConfig } from "../../domain/entities";
7
+ import { streamChatCompletion } from "../../infrastructure/http";
8
+ import { RequestBuilder } from "../../shared/request-builder";
9
+ import { logger } from "../../shared/logger";
10
+
11
+ export interface StreamingCallbacks {
12
+ onChunk?: (chunk: string) => void;
13
+ onComplete?: (fullContent: string) => void;
14
+ onError?: (error: Error) => void;
15
+ }
16
+
17
+ export interface StreamingOptions {
18
+ model?: string;
19
+ generationConfig?: GroqGenerationConfig;
20
+ callbacks?: StreamingCallbacks;
21
+ }
22
+
23
+ export async function* streamText(
24
+ prompt: string,
25
+ options: StreamingOptions = {}
26
+ ): AsyncGenerator<string> {
27
+ logger.debug("Streaming", "Starting", {
28
+ model: options.model,
29
+ promptLength: prompt.length,
30
+ });
31
+
32
+ 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
+
38
+ let fullContent = "";
39
+
40
+ try {
41
+ for await (const chunk of streamChatCompletion(request, config)) {
42
+ const content = chunk.choices[0]?.delta?.content;
43
+ if (content) {
44
+ fullContent += content;
45
+ options.callbacks?.onChunk?.(content);
46
+ yield content;
47
+ }
48
+ }
49
+
50
+ options.callbacks?.onComplete?.(fullContent);
51
+
52
+ logger.debug("Streaming", "Complete", {
53
+ totalLength: fullContent.length,
54
+ });
55
+ } catch (error) {
56
+ options.callbacks?.onError?.(error as Error);
57
+ throw error;
58
+ }
59
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Structured Generation Use Case
3
+ * Generates structured JSON output from prompts
4
+ */
5
+
6
+ import type { GroqGenerationConfig, GroqMessage } from "../../domain/entities";
7
+ import { groqHttpClient } from "../../infrastructure/http";
8
+ import { RequestBuilder } from "../../shared/request-builder";
9
+ import { ResponseHandler } from "../../shared/response-handler";
10
+ import { Timer, logger } from "../../shared/logger";
11
+ import { GroqError, GroqErrorType } from "../../domain/entities/error.types";
12
+ import { cleanJsonResponse } from "../../utils/content-mapper.util";
13
+
14
+ export interface StructuredGenerationOptions<T> {
15
+ model?: string;
16
+ generationConfig?: GroqGenerationConfig;
17
+ schema?: Record<string, unknown>;
18
+ example?: T;
19
+ }
20
+
21
+ export async function generateStructured<T = Record<string, unknown>>(
22
+ prompt: string,
23
+ options: StructuredGenerationOptions<T> = {}
24
+ ): Promise<T> {
25
+ const timer = new Timer();
26
+
27
+ logger.debug("StructuredGeneration", "Called", {
28
+ model: options.model,
29
+ hasSchema: !!options.schema,
30
+ hasExample: !!options.example,
31
+ });
32
+
33
+ const systemPrompt = buildSystemPrompt(options.schema, options.example);
34
+ const request = RequestBuilder.buildSystemPromptRequest(systemPrompt, prompt, {
35
+ ...options,
36
+ defaultTemperature: 0.3,
37
+ defaultMaxTokens: 2048,
38
+ });
39
+
40
+ timer.startApiCall();
41
+ const response = await groqHttpClient.chatCompletion(request);
42
+ timer.endApiCall();
43
+
44
+ const result = timer.getResult();
45
+ logger.debug("StructuredGeneration", "API response", {
46
+ apiDuration: Timer.format(result.apiMs),
47
+ });
48
+
49
+ let content = ResponseHandler.extractContent(response);
50
+ content = cleanJsonResponse(content);
51
+
52
+ logger.debug("StructuredGeneration", "Parsing JSON");
53
+
54
+ try {
55
+ const parsed = JSON.parse(content) as T;
56
+
57
+ logger.debug("StructuredGeneration", "Complete", {
58
+ totalDuration: Timer.format(result.totalMs),
59
+ parsedKeys: Object.keys(parsed),
60
+ });
61
+
62
+ return parsed;
63
+ } catch (error) {
64
+ logger.error("StructuredGeneration", "JSON parse failed", {
65
+ error,
66
+ contentLength: content.length,
67
+ });
68
+
69
+ throw new GroqError(
70
+ GroqErrorType.UNKNOWN_ERROR,
71
+ `Failed to parse JSON: ${content}`,
72
+ error
73
+ );
74
+ }
75
+ }
76
+
77
+ function buildSystemPrompt<T>(
78
+ schema?: Record<string, unknown>,
79
+ example?: T
80
+ ): string {
81
+ let prompt = "You are a helpful assistant that generates valid JSON output.";
82
+
83
+ if (schema) {
84
+ prompt += `\n\nResponse must conform to this JSON schema:\n${JSON.stringify(schema, null, 2)}`;
85
+ }
86
+
87
+ if (example) {
88
+ prompt += `\n\nExample response format:\n${JSON.stringify(example, null, 2)}`;
89
+ }
90
+
91
+ prompt += "\n\nIMPORTANT: Respond ONLY with valid JSON. No markdown, no code blocks.";
92
+
93
+ return prompt;
94
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Text Generation Use Case
3
+ * Handles simple text generation from prompts
4
+ */
5
+
6
+ import type { GroqGenerationConfig } from "../../domain/entities";
7
+ import { groqHttpClient } from "../../infrastructure/http";
8
+ import { RequestBuilder } from "../../shared/request-builder";
9
+ import { ResponseHandler } from "../../shared/response-handler";
10
+ import { Timer, logger } from "../../shared";
11
+
12
+ export interface TextGenerationOptions {
13
+ model?: string;
14
+ generationConfig?: GroqGenerationConfig;
15
+ }
16
+
17
+ export async function generateText(
18
+ prompt: string,
19
+ options: TextGenerationOptions = {}
20
+ ): Promise<string> {
21
+ const timer = new Timer();
22
+
23
+ logger.debug("TextGeneration", "Called", {
24
+ model: options.model,
25
+ promptLength: prompt.length,
26
+ });
27
+
28
+ const request = RequestBuilder.buildPromptRequest(prompt, options);
29
+
30
+ logger.debug("TextGeneration", "Sending request", {
31
+ endpoint: "/v1/chat/completions",
32
+ model: request.model,
33
+ });
34
+
35
+ timer.startApiCall();
36
+ const response = await groqHttpClient.chatCompletion(request);
37
+ timer.endApiCall();
38
+
39
+ const result = timer.getResult();
40
+ ResponseHandler.logResponse(logger, response, result.apiMs);
41
+
42
+ const handled = ResponseHandler.handleResponse(response);
43
+
44
+ logger.debug("TextGeneration", "Complete", {
45
+ totalDuration: Timer.format(result.totalMs),
46
+ responseLength: handled.content.length,
47
+ });
48
+
49
+ return handled.content;
50
+ }
package/src/index.ts CHANGED
@@ -2,11 +2,18 @@
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
+ *
5
12
  * @author umituz
6
13
  * @license MIT
7
14
  */
8
15
 
9
- // Domain Types
16
+ // Domain Layer
10
17
  export type {
11
18
  GroqConfig,
12
19
  GroqGenerationConfig,
@@ -42,32 +49,47 @@ export {
42
49
  type ModelInfo,
43
50
  } from "./domain/entities/models";
44
51
 
45
- // Services
46
- export { groqHttpClient } from "./infrastructure/services/GroqClient";
47
- export { textGeneration, chatGeneration } from "./infrastructure/services/TextGeneration";
48
- export { structuredText, structuredChat } from "./infrastructure/services/StructuredText";
49
- export { streaming, streamingChat } from "./infrastructure/services/Streaming";
52
+ // Application Layer (Use Cases)
50
53
  export {
51
- chatSessionService,
52
- createChatSession,
53
- sendChatMessage,
54
- buildChatHistory,
55
- trimChatHistory,
54
+ generateText,
55
+ generateStructured,
56
+ streamText,
57
+ chatSessionManager,
58
+ type TextGenerationOptions,
59
+ type StructuredGenerationOptions,
60
+ type StreamingCallbacks,
61
+ type StreamingOptions,
56
62
  type ChatSession,
57
- type SendChatMessageOptions,
58
63
  type ChatSendResult,
59
- type ChatHistoryMessage,
60
- } from "./infrastructure/services";
64
+ } from "./application/use-cases";
61
65
 
62
- export type { StreamingCallbacks, StreamingOptions } from "./infrastructure/services/Streaming";
66
+ // Infrastructure Layer
67
+ export {
68
+ groqHttpClient,
69
+ streamChatCompletion,
70
+ } from "./infrastructure/http";
63
71
 
64
- // React Hooks
65
- export { useGroq } from "./presentation/hooks/useGroq";
66
- export type { UseGroqOptions, UseGroqReturn } from "./presentation/hooks/useGroq";
72
+ // Presentation Layer
73
+ export {
74
+ useGroq,
75
+ type UseGroqOptions,
76
+ type UseGroqReturn,
77
+ } from "./presentation";
67
78
 
68
- export { useOperationManager } from "./presentation/hooks/useOperationManager";
79
+ // Shared Layer
80
+ export {
81
+ logger,
82
+ LogLevel,
83
+ Timer,
84
+ RequestBuilder,
85
+ ResponseHandler,
86
+ type LogContext,
87
+ type TimerResult,
88
+ type RequestBuilderOptions,
89
+ type ResponseHandlerResult,
90
+ } from "./shared";
69
91
 
70
- // Provider Configuration & Factory
92
+ // Provider Factory
71
93
  export {
72
94
  ConfigBuilder,
73
95
  GenerationConfigBuilder,
@@ -75,11 +97,8 @@ export {
75
97
  initializeProvider,
76
98
  configureProvider,
77
99
  resetProvider,
78
- } from "./providers/ProviderFactory";
79
-
80
- export type {
81
- ProviderConfig,
82
- ProviderFactoryOptions,
100
+ type ProviderConfig,
101
+ type ProviderFactoryOptions,
83
102
  } from "./providers/ProviderFactory";
84
103
 
85
104
  // Utilities
@@ -91,21 +110,22 @@ export {
91
110
  promptToMessages,
92
111
  extractTextFromMessages,
93
112
  formatMessagesForDisplay,
94
- } from "./infrastructure/utils/content-mapper.util";
113
+ cleanJsonResponse,
114
+ } from "./utils/content-mapper.util";
95
115
 
96
116
  export {
97
117
  getUserFriendlyError,
98
118
  isRetryableError,
99
119
  isAuthError,
100
120
  formatErrorForLogging,
101
- } from "./infrastructure/utils/error-mapper.util";
121
+ } from "./utils/error-mapper.util";
102
122
 
103
123
  export {
104
124
  executeWithState,
105
125
  executeWithRetry,
106
126
  type AsyncStateSetters,
107
127
  type AsyncCallbacks,
108
- } from "./infrastructure/utils/async";
128
+ } from "./utils/async";
109
129
 
110
130
  export {
111
131
  generateRandomId,
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Groq HTTP Client
3
+ * Core HTTP client for Groq API - simplified and focused
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 { logger } from "../../shared/logger";
14
+
15
+ const DEFAULT_BASE_URL = "https://api.groq.com/openai/v1";
16
+ const DEFAULT_TIMEOUT = 60000;
17
+
18
+ class GroqHttpClient {
19
+ private config: GroqConfig | null = null;
20
+ private initialized = false;
21
+
22
+ initialize(config: GroqConfig): void {
23
+ const apiKey = config.apiKey?.trim();
24
+
25
+ logger.debug("GroqClient", "Initializing", {
26
+ hasApiKey: !!apiKey,
27
+ keyLength: apiKey?.length,
28
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL,
29
+ });
30
+
31
+ if (!apiKey || apiKey.length < 10) {
32
+ throw new GroqError(
33
+ GroqErrorType.INVALID_API_KEY,
34
+ "API key is required and must be at least 10 characters"
35
+ );
36
+ }
37
+
38
+ this.config = {
39
+ apiKey,
40
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL,
41
+ timeoutMs: config.timeoutMs || DEFAULT_TIMEOUT,
42
+ textModel: config.textModel,
43
+ };
44
+ this.initialized = true;
45
+
46
+ logger.debug("GroqClient", "Initialization complete", {
47
+ initialized: this.initialized,
48
+ });
49
+ }
50
+
51
+ isInitialized(): boolean {
52
+ return this.initialized && this.config !== null;
53
+ }
54
+
55
+ getConfig(): GroqConfig {
56
+ if (!this.config || !this.initialized) {
57
+ throw new GroqError(
58
+ GroqErrorType.MISSING_CONFIG,
59
+ "Client not initialized"
60
+ );
61
+ }
62
+ return this.config;
63
+ }
64
+
65
+ private async request<T>(endpoint: string, body: unknown): Promise<T> {
66
+ if (!this.config || !this.initialized) {
67
+ throw new GroqError(GroqErrorType.MISSING_CONFIG, "Client not initialized");
68
+ }
69
+
70
+ const url = `${this.config.baseUrl}${endpoint}`;
71
+ const timeout = this.config.timeoutMs || DEFAULT_TIMEOUT;
72
+
73
+ logger.debug("GroqClient", "API Request", {
74
+ endpoint,
75
+ timeout: `${timeout}ms`,
76
+ });
77
+
78
+ const controller = new AbortController();
79
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
80
+
81
+ try {
82
+ const response = await fetch(url, {
83
+ method: "POST",
84
+ headers: {
85
+ "Content-Type": "application/json",
86
+ "Authorization": `Bearer ${this.config.apiKey}`,
87
+ },
88
+ body: JSON.stringify(body),
89
+ signal: controller.signal,
90
+ });
91
+
92
+ clearTimeout(timeoutId);
93
+
94
+ if (!response.ok) {
95
+ await this.handleErrorResponse(response);
96
+ }
97
+
98
+ return (await response.json()) as T;
99
+ } catch (error) {
100
+ throw this.handleRequestError(error);
101
+ }
102
+ }
103
+
104
+ private async handleErrorResponse(response: Response): Promise<never> {
105
+ let errorMessage = `HTTP ${response.status}`;
106
+ const errorType = mapHttpStatusToErrorType(response.status);
107
+
108
+ try {
109
+ const errorData = (await response.json()) as { error?: { message?: string } };
110
+ if (errorData.error?.message) {
111
+ errorMessage = errorData.error.message;
112
+ }
113
+ } catch {
114
+ // Use default message
115
+ }
116
+
117
+ throw new GroqError(errorType, errorMessage);
118
+ }
119
+
120
+ private handleRequestError(error: unknown): GroqError {
121
+ if (error instanceof GroqError) return error;
122
+
123
+ if (error instanceof Error) {
124
+ if (error.name === "AbortError") {
125
+ return new GroqError(GroqErrorType.ABORT_ERROR, "Request aborted", error);
126
+ }
127
+ if (error.message.includes("network")) {
128
+ return new GroqError(GroqErrorType.NETWORK_ERROR, "Network error", error);
129
+ }
130
+ }
131
+
132
+ return new GroqError(
133
+ GroqErrorType.UNKNOWN_ERROR,
134
+ error instanceof Error ? error.message : "Unknown error"
135
+ );
136
+ }
137
+
138
+ async chatCompletion(request: GroqChatRequest): Promise<GroqChatResponse> {
139
+ return this.request<GroqChatResponse>("/chat/completions", {
140
+ ...request,
141
+ stream: false,
142
+ });
143
+ }
144
+
145
+ reset(): void {
146
+ this.config = null;
147
+ this.initialized = false;
148
+ }
149
+ }
150
+
151
+ export const groqHttpClient = new GroqHttpClient();
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Infrastructure HTTP Layer
3
+ * HTTP communication with Groq API
4
+ */
5
+
6
+ export { groqHttpClient } from "./groq-http-client";
7
+ export { streamChatCompletion } from "./streaming-client";