@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
package/package.json
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
60
|
-
} from "./infrastructure/services";
|
|
64
|
+
} from "./application/use-cases";
|
|
61
65
|
|
|
62
|
-
|
|
66
|
+
// Infrastructure Layer
|
|
67
|
+
export {
|
|
68
|
+
groqHttpClient,
|
|
69
|
+
streamChatCompletion,
|
|
70
|
+
} from "./infrastructure/http";
|
|
63
71
|
|
|
64
|
-
//
|
|
65
|
-
export {
|
|
66
|
-
|
|
72
|
+
// Presentation Layer
|
|
73
|
+
export {
|
|
74
|
+
useGroq,
|
|
75
|
+
type UseGroqOptions,
|
|
76
|
+
type UseGroqReturn,
|
|
77
|
+
} from "./presentation";
|
|
67
78
|
|
|
68
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 "./
|
|
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 "./
|
|
128
|
+
} from "./utils/async";
|
|
109
129
|
|
|
110
130
|
export {
|
|
111
131
|
generateRandomId,
|
|
@@ -121,7 +141,7 @@ export {
|
|
|
121
141
|
calculateRequestTimeout,
|
|
122
142
|
calculateTransferRate,
|
|
123
143
|
calculateAverage,
|
|
124
|
-
} from "./
|
|
144
|
+
} from "./utils/calculation.util";
|
|
125
145
|
|
|
126
146
|
export {
|
|
127
147
|
telemetry,
|
|
@@ -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();
|