@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.
- package/package.json +1 -1
- package/src/application/use-cases/chat-session.usecase.ts +137 -0
- package/src/application/use-cases/index.ts +19 -0
- package/src/application/use-cases/streaming.usecase.ts +59 -0
- package/src/application/use-cases/structured-generation.usecase.ts +94 -0
- package/src/application/use-cases/text-generation.usecase.ts +50 -0
- package/src/index.ts +49 -29
- package/src/infrastructure/http/groq-http-client.ts +151 -0
- package/src/infrastructure/http/index.ts +7 -0
- package/src/infrastructure/http/streaming-client.ts +125 -0
- package/src/presentation/hooks/index.ts +7 -0
- package/src/presentation/hooks/use-groq.hook.ts +184 -0
- package/src/presentation/index.ts +6 -0
- package/src/shared/index.ts +16 -0
- package/src/shared/logger.ts +63 -0
- package/src/shared/request-builder.ts +64 -0
- package/src/shared/timer.ts +43 -0
- package/src/infrastructure/services/ChatSession.ts +0 -316
- package/src/infrastructure/services/GroqClient.ts +0 -351
- package/src/infrastructure/services/Streaming.ts +0 -104
- package/src/infrastructure/services/StructuredText.ts +0 -245
- package/src/infrastructure/services/TextGeneration.ts +0 -163
- package/src/infrastructure/services/index.ts +0 -19
|
@@ -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();
|