@umituz/react-native-ai-groq-provider 1.0.23 → 1.0.25
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 +19 -9
- package/src/application/use-cases/streaming.usecase.ts +13 -7
- package/src/application/use-cases/structured-generation.usecase.ts +17 -5
- package/src/application/use-cases/text-generation.usecase.ts +4 -3
- package/src/domain/entities/error.types.ts +17 -2
- package/src/index.ts +26 -68
- package/src/infrastructure/http/groq-http-client.ts +68 -12
- package/src/infrastructure/http/streaming-client.ts +135 -84
- package/src/infrastructure/telemetry/TelemetryHooks.ts +40 -23
- package/src/infrastructure/utils/calculation.util.ts +46 -14
- package/src/infrastructure/utils/content-mapper.util.ts +1 -1
- package/src/presentation/hooks/use-groq.hook.ts +14 -22
- package/src/providers/ConfigBuilder.ts +2 -73
- package/src/providers/ProviderFactory.ts +7 -62
- package/src/shared/request-builder.ts +8 -4
- package/src/shared/response-handler.ts +81 -0
- package/src/types/react-native-global.d.ts +12 -0
- package/src/application/use-cases/index.ts +0 -19
- package/src/domain/entities/index.ts +0 -7
- package/src/infrastructure/http/index.ts +0 -7
- package/src/infrastructure/telemetry/index.ts +0 -5
- package/src/infrastructure/utils/async/index.ts +0 -6
- package/src/infrastructure/utils/index.ts +0 -8
- package/src/presentation/hooks/index.ts +0 -7
- package/src/presentation/hooks/useGroq.ts +0 -235
- package/src/presentation/hooks/useOperationManager.ts +0 -119
- package/src/presentation/index.ts +0 -6
- package/src/providers/index.ts +0 -16
- package/src/shared/index.ts +0 -16
package/package.json
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
* Manages multi-turn chat conversations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { GroqMessage, GroqChatConfig } from "../../domain/entities";
|
|
7
|
-
import {
|
|
6
|
+
import type { GroqMessage, GroqChatConfig } from "../../domain/entities/groq.types";
|
|
7
|
+
import { DEFAULT_MODELS } from "../../domain/entities/groq.types";
|
|
8
|
+
import { groqHttpClient } from "../../infrastructure/http/groq-http-client";
|
|
8
9
|
import { RequestBuilder } from "../../shared/request-builder";
|
|
9
10
|
import { ResponseHandler } from "../../shared/response-handler";
|
|
10
11
|
import { logger } from "../../shared/logger";
|
|
11
12
|
import { GroqError, GroqErrorType } from "../../domain/entities/error.types";
|
|
12
|
-
import { generateSessionId } from "../../utils/calculation.util";
|
|
13
|
+
import { generateSessionId } from "../../infrastructure/utils/calculation.util";
|
|
13
14
|
|
|
14
15
|
export interface ChatSession {
|
|
15
16
|
id: string;
|
|
@@ -40,7 +41,7 @@ class ChatSessionManager {
|
|
|
40
41
|
|
|
41
42
|
const session: ChatSession = {
|
|
42
43
|
id: generateSessionId("groq-chat"),
|
|
43
|
-
model: config.model ||
|
|
44
|
+
model: config.model || DEFAULT_MODELS.TEXT,
|
|
44
45
|
systemInstruction: config.systemInstruction,
|
|
45
46
|
messages: config.history ? [...config.history] : [],
|
|
46
47
|
createdAt: new Date(),
|
|
@@ -50,11 +51,7 @@ class ChatSessionManager {
|
|
|
50
51
|
this.sessions.set(session.id, session);
|
|
51
52
|
|
|
52
53
|
if (this.sessions.size > this.MAX_SESSIONS) {
|
|
53
|
-
|
|
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));
|
|
54
|
+
this.removeOldestSessions();
|
|
58
55
|
}
|
|
59
56
|
|
|
60
57
|
return session;
|
|
@@ -82,6 +79,19 @@ class ChatSessionManager {
|
|
|
82
79
|
expiredIds.forEach((id) => this.sessions.delete(id));
|
|
83
80
|
}
|
|
84
81
|
|
|
82
|
+
private removeOldestSessions(): void {
|
|
83
|
+
const excessCount = this.sessions.size - this.MAX_SESSIONS;
|
|
84
|
+
if (excessCount <= 0) return;
|
|
85
|
+
|
|
86
|
+
// Sort by creation date and remove oldest
|
|
87
|
+
const sorted = Array.from(this.sessions.entries())
|
|
88
|
+
.sort(([, a], [, b]) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
89
|
+
|
|
90
|
+
for (let i = 0; i < excessCount; i++) {
|
|
91
|
+
this.sessions.delete(sorted[i][0]);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
85
95
|
async send(sessionId: string, content: string): Promise<ChatSendResult> {
|
|
86
96
|
const session = this.sessions.get(sessionId);
|
|
87
97
|
if (!session) {
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* Handles streaming text generation
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { GroqGenerationConfig } from "../../domain/entities";
|
|
7
|
-
import { streamChatCompletion } from "../../infrastructure/http";
|
|
6
|
+
import type { GroqGenerationConfig } from "../../domain/entities/groq.types";
|
|
7
|
+
import { streamChatCompletion } from "../../infrastructure/http/streaming-client";
|
|
8
8
|
import { RequestBuilder } from "../../shared/request-builder";
|
|
9
9
|
import { logger } from "../../shared/logger";
|
|
10
|
+
import { groqHttpClient } from "../../infrastructure/http/groq-http-client";
|
|
10
11
|
|
|
11
12
|
export interface StreamingCallbacks {
|
|
12
13
|
onChunk?: (chunk: string) => void;
|
|
@@ -29,16 +30,21 @@ export async function* streamText(
|
|
|
29
30
|
promptLength: prompt.length,
|
|
30
31
|
});
|
|
31
32
|
|
|
33
|
+
if (!groqHttpClient.isInitialized()) {
|
|
34
|
+
throw new Error("Groq client not initialized. Call initializeProvider() first.");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const config = groqHttpClient.getConfig();
|
|
32
38
|
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
39
|
|
|
38
40
|
let fullContent = "";
|
|
39
41
|
|
|
40
42
|
try {
|
|
41
|
-
for await (const chunk of streamChatCompletion(request,
|
|
43
|
+
for await (const chunk of streamChatCompletion(request, {
|
|
44
|
+
apiKey: config.apiKey,
|
|
45
|
+
baseUrl: config.baseUrl,
|
|
46
|
+
timeoutMs: config.timeoutMs,
|
|
47
|
+
})) {
|
|
42
48
|
const content = chunk.choices[0]?.delta?.content;
|
|
43
49
|
if (content) {
|
|
44
50
|
fullContent += content;
|
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
* Generates structured JSON output from prompts
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { GroqGenerationConfig
|
|
7
|
-
import { groqHttpClient } from "../../infrastructure/http";
|
|
6
|
+
import type { GroqGenerationConfig } from "../../domain/entities/groq.types";
|
|
7
|
+
import { groqHttpClient } from "../../infrastructure/http/groq-http-client";
|
|
8
8
|
import { RequestBuilder } from "../../shared/request-builder";
|
|
9
|
+
import { Timer } from "../../shared/timer";
|
|
10
|
+
import { logger } from "../../shared/logger";
|
|
9
11
|
import { ResponseHandler } from "../../shared/response-handler";
|
|
10
|
-
import { Timer, logger } from "../../shared/logger";
|
|
11
12
|
import { GroqError, GroqErrorType } from "../../domain/entities/error.types";
|
|
12
|
-
import { cleanJsonResponse } from "../../utils/content-mapper.util";
|
|
13
|
+
import { cleanJsonResponse } from "../../infrastructure/utils/content-mapper.util";
|
|
14
|
+
|
|
15
|
+
const MAX_CONTENT_LENGTH_FOR_ERROR = 200; // Truncate content in error messages
|
|
13
16
|
|
|
14
17
|
export interface StructuredGenerationOptions<T> {
|
|
15
18
|
model?: string;
|
|
@@ -54,6 +57,11 @@ export async function generateStructured<T = Record<string, unknown>>(
|
|
|
54
57
|
try {
|
|
55
58
|
const parsed = JSON.parse(content) as T;
|
|
56
59
|
|
|
60
|
+
// Validate that result is an object
|
|
61
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
62
|
+
throw new Error("Response is not a valid object");
|
|
63
|
+
}
|
|
64
|
+
|
|
57
65
|
logger.debug("StructuredGeneration", "Complete", {
|
|
58
66
|
totalDuration: Timer.format(result.totalMs),
|
|
59
67
|
parsedKeys: Object.keys(parsed),
|
|
@@ -66,9 +74,13 @@ export async function generateStructured<T = Record<string, unknown>>(
|
|
|
66
74
|
contentLength: content.length,
|
|
67
75
|
});
|
|
68
76
|
|
|
77
|
+
const truncatedContent = content.length > MAX_CONTENT_LENGTH_FOR_ERROR
|
|
78
|
+
? content.substring(0, MAX_CONTENT_LENGTH_FOR_ERROR) + "..."
|
|
79
|
+
: content;
|
|
80
|
+
|
|
69
81
|
throw new GroqError(
|
|
70
82
|
GroqErrorType.UNKNOWN_ERROR,
|
|
71
|
-
`Failed to parse JSON: ${
|
|
83
|
+
`Failed to parse JSON response. Expected valid JSON object but got: ${truncatedContent}`,
|
|
72
84
|
error
|
|
73
85
|
);
|
|
74
86
|
}
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
* Handles simple text generation from prompts
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { GroqGenerationConfig } from "../../domain/entities";
|
|
7
|
-
import { groqHttpClient } from "../../infrastructure/http";
|
|
6
|
+
import type { GroqGenerationConfig } from "../../domain/entities/groq.types";
|
|
7
|
+
import { groqHttpClient } from "../../infrastructure/http/groq-http-client";
|
|
8
8
|
import { RequestBuilder } from "../../shared/request-builder";
|
|
9
9
|
import { ResponseHandler } from "../../shared/response-handler";
|
|
10
|
-
import { Timer
|
|
10
|
+
import { Timer } from "../../shared/timer";
|
|
11
|
+
import { logger } from "../../shared/logger";
|
|
11
12
|
|
|
12
13
|
export interface TextGenerationOptions {
|
|
13
14
|
model?: string;
|
|
@@ -22,7 +22,7 @@ export enum GroqErrorType {
|
|
|
22
22
|
MISSING_CONFIG = "MISSING_CONFIG",
|
|
23
23
|
/** Network error occurred */
|
|
24
24
|
NETWORK_ERROR = "NETWORK_ERROR",
|
|
25
|
-
/** Request was aborted */
|
|
25
|
+
/** Request was aborted or timed out */
|
|
26
26
|
ABORT_ERROR = "ABORT_ERROR",
|
|
27
27
|
/** Rate limit exceeded */
|
|
28
28
|
RATE_LIMIT_ERROR = "RATE_LIMIT_ERROR",
|
|
@@ -30,6 +30,8 @@ export enum GroqErrorType {
|
|
|
30
30
|
QUOTA_EXCEEDED = "QUOTA_EXCEEDED",
|
|
31
31
|
/** Server error */
|
|
32
32
|
SERVER_ERROR = "SERVER_ERROR",
|
|
33
|
+
/** Client error (invalid request) */
|
|
34
|
+
CLIENT_ERROR = "CLIENT_ERROR",
|
|
33
35
|
/** Unknown error occurred */
|
|
34
36
|
UNKNOWN_ERROR = "UNKNOWN_ERROR",
|
|
35
37
|
}
|
|
@@ -38,17 +40,30 @@ export enum GroqErrorType {
|
|
|
38
40
|
* Map HTTP status codes to error types
|
|
39
41
|
*/
|
|
40
42
|
export function mapHttpStatusToErrorType(status: number): GroqErrorType {
|
|
43
|
+
// Authentication errors
|
|
41
44
|
if (status === 401 || status === 403) {
|
|
42
45
|
return GroqErrorType.INVALID_API_KEY;
|
|
43
46
|
}
|
|
47
|
+
|
|
48
|
+
// Rate limiting
|
|
44
49
|
if (status === 429) {
|
|
45
50
|
return GroqErrorType.RATE_LIMIT_ERROR;
|
|
46
51
|
}
|
|
52
|
+
|
|
53
|
+
// Quota exceeded
|
|
54
|
+
if (status === 402) {
|
|
55
|
+
return GroqErrorType.QUOTA_EXCEEDED;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Server errors
|
|
47
59
|
if (status >= 500 && status < 600) {
|
|
48
60
|
return GroqErrorType.SERVER_ERROR;
|
|
49
61
|
}
|
|
62
|
+
|
|
63
|
+
// Client errors (4xx except specific cases above)
|
|
50
64
|
if (status >= 400 && status < 500) {
|
|
51
|
-
return GroqErrorType.
|
|
65
|
+
return GroqErrorType.CLIENT_ERROR;
|
|
52
66
|
}
|
|
67
|
+
|
|
53
68
|
return GroqErrorType.UNKNOWN_ERROR;
|
|
54
69
|
}
|
package/src/index.ts
CHANGED
|
@@ -2,18 +2,11 @@
|
|
|
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
|
-
*
|
|
12
5
|
* @author umituz
|
|
13
6
|
* @license MIT
|
|
14
7
|
*/
|
|
15
8
|
|
|
16
|
-
// Domain Layer
|
|
9
|
+
// Domain Layer - Direct exports, no barrel re-exports
|
|
17
10
|
export type {
|
|
18
11
|
GroqConfig,
|
|
19
12
|
GroqGenerationConfig,
|
|
@@ -28,12 +21,9 @@ export type {
|
|
|
28
21
|
GroqChunkChoice,
|
|
29
22
|
GroqErrorResponse,
|
|
30
23
|
GroqChatConfig,
|
|
31
|
-
} from "./domain/entities";
|
|
24
|
+
} from "./domain/entities/groq.types";
|
|
32
25
|
|
|
33
|
-
export {
|
|
34
|
-
GROQ_MODELS,
|
|
35
|
-
DEFAULT_MODELS,
|
|
36
|
-
} from "./domain/entities";
|
|
26
|
+
export { GROQ_MODELS, DEFAULT_MODELS } from "./domain/entities/groq.types";
|
|
37
27
|
|
|
38
28
|
export {
|
|
39
29
|
GroqError,
|
|
@@ -49,59 +39,30 @@ export {
|
|
|
49
39
|
type ModelInfo,
|
|
50
40
|
} from "./domain/entities/models";
|
|
51
41
|
|
|
52
|
-
// Application Layer
|
|
53
|
-
export {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
chatSessionManager,
|
|
58
|
-
type TextGenerationOptions,
|
|
59
|
-
type StructuredGenerationOptions,
|
|
60
|
-
type StreamingCallbacks,
|
|
61
|
-
type StreamingOptions,
|
|
62
|
-
type ChatSession,
|
|
63
|
-
type ChatSendResult,
|
|
64
|
-
} from "./application/use-cases";
|
|
42
|
+
// Application Layer - Direct exports
|
|
43
|
+
export { generateText, type TextGenerationOptions } from "./application/use-cases/text-generation.usecase";
|
|
44
|
+
export { generateStructured, type StructuredGenerationOptions } from "./application/use-cases/structured-generation.usecase";
|
|
45
|
+
export { streamText, type StreamingCallbacks, type StreamingOptions } from "./application/use-cases/streaming.usecase";
|
|
46
|
+
export { chatSessionManager, type ChatSession, type ChatSendResult } from "./application/use-cases/chat-session.usecase";
|
|
65
47
|
|
|
66
|
-
// Infrastructure Layer
|
|
67
|
-
export {
|
|
68
|
-
|
|
69
|
-
streamChatCompletion,
|
|
70
|
-
} from "./infrastructure/http";
|
|
48
|
+
// Infrastructure Layer - Direct exports
|
|
49
|
+
export { groqHttpClient } from "./infrastructure/http/groq-http-client";
|
|
50
|
+
export { streamChatCompletion } from "./infrastructure/http/streaming-client";
|
|
71
51
|
|
|
72
|
-
// Presentation Layer
|
|
73
|
-
export {
|
|
74
|
-
useGroq,
|
|
75
|
-
type UseGroqOptions,
|
|
76
|
-
type UseGroqReturn,
|
|
77
|
-
} from "./presentation";
|
|
52
|
+
// Presentation Layer - Direct exports
|
|
53
|
+
export { useGroq, type UseGroqOptions, type UseGroqReturn } from "./presentation/hooks/use-groq.hook";
|
|
78
54
|
|
|
79
|
-
// Shared Layer
|
|
80
|
-
export {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
RequestBuilder,
|
|
85
|
-
ResponseHandler,
|
|
86
|
-
type LogContext,
|
|
87
|
-
type TimerResult,
|
|
88
|
-
type RequestBuilderOptions,
|
|
89
|
-
type ResponseHandlerResult,
|
|
90
|
-
} from "./shared";
|
|
55
|
+
// Shared Layer - Direct exports
|
|
56
|
+
export { logger, LogLevel, type LogContext } from "./shared/logger";
|
|
57
|
+
export { Timer, type TimerResult } from "./shared/timer";
|
|
58
|
+
export { RequestBuilder, type RequestBuilderOptions } from "./shared/request-builder";
|
|
59
|
+
export { ResponseHandler, type ResponseHandlerResult } from "./shared/response-handler";
|
|
91
60
|
|
|
92
|
-
// Provider Factory
|
|
93
|
-
export {
|
|
94
|
-
|
|
95
|
-
GenerationConfigBuilder,
|
|
96
|
-
providerFactory,
|
|
97
|
-
initializeProvider,
|
|
98
|
-
configureProvider,
|
|
99
|
-
resetProvider,
|
|
100
|
-
type ProviderConfig,
|
|
101
|
-
type ProviderFactoryOptions,
|
|
102
|
-
} from "./providers/ProviderFactory";
|
|
61
|
+
// Provider Factory - Direct exports, no wrappers
|
|
62
|
+
export { ConfigBuilder, GenerationConfigBuilder } from "./providers/ConfigBuilder";
|
|
63
|
+
export { initializeProvider, resetProvider, isProviderInitialized, type ProviderConfig } from "./providers/ProviderFactory";
|
|
103
64
|
|
|
104
|
-
// Utilities
|
|
65
|
+
// Utilities - Direct exports, no barrel re-exports
|
|
105
66
|
export {
|
|
106
67
|
createUserMessage,
|
|
107
68
|
createAssistantMessage,
|
|
@@ -111,21 +72,21 @@ export {
|
|
|
111
72
|
extractTextFromMessages,
|
|
112
73
|
formatMessagesForDisplay,
|
|
113
74
|
cleanJsonResponse,
|
|
114
|
-
} from "./utils/content-mapper.util";
|
|
75
|
+
} from "./infrastructure/utils/content-mapper.util";
|
|
115
76
|
|
|
116
77
|
export {
|
|
117
78
|
getUserFriendlyError,
|
|
118
79
|
isRetryableError,
|
|
119
80
|
isAuthError,
|
|
120
81
|
formatErrorForLogging,
|
|
121
|
-
} from "./utils/error-mapper.util";
|
|
82
|
+
} from "./infrastructure/utils/error-mapper.util";
|
|
122
83
|
|
|
123
84
|
export {
|
|
124
85
|
executeWithState,
|
|
125
86
|
executeWithRetry,
|
|
126
87
|
type AsyncStateSetters,
|
|
127
88
|
type AsyncCallbacks,
|
|
128
|
-
} from "./utils/async";
|
|
89
|
+
} from "./infrastructure/utils/async/execute-state.util";
|
|
129
90
|
|
|
130
91
|
export {
|
|
131
92
|
generateRandomId,
|
|
@@ -143,7 +104,4 @@ export {
|
|
|
143
104
|
calculateAverage,
|
|
144
105
|
} from "./infrastructure/utils/calculation.util";
|
|
145
106
|
|
|
146
|
-
export {
|
|
147
|
-
telemetry,
|
|
148
|
-
useTelemetry,
|
|
149
|
-
} from "./infrastructure/telemetry";
|
|
107
|
+
export { telemetry, useTelemetry } from "./infrastructure/telemetry/TelemetryHooks";
|
|
@@ -7,13 +7,14 @@ import type {
|
|
|
7
7
|
GroqConfig,
|
|
8
8
|
GroqChatRequest,
|
|
9
9
|
GroqChatResponse,
|
|
10
|
-
|
|
11
|
-
} from "../../domain/entities";
|
|
10
|
+
} from "../../domain/entities/groq.types";
|
|
12
11
|
import { GroqError, GroqErrorType, mapHttpStatusToErrorType } from "../../domain/entities/error.types";
|
|
13
12
|
import { logger } from "../../shared/logger";
|
|
14
13
|
|
|
15
14
|
const DEFAULT_BASE_URL = "https://api.groq.com/openai/v1";
|
|
16
15
|
const DEFAULT_TIMEOUT = 60000;
|
|
16
|
+
const MIN_API_KEY_LENGTH = 10;
|
|
17
|
+
const GROQ_KEY_PREFIX = "gsk_";
|
|
17
18
|
|
|
18
19
|
class GroqHttpClient {
|
|
19
20
|
private config: GroqConfig | null = null;
|
|
@@ -28,17 +29,30 @@ class GroqHttpClient {
|
|
|
28
29
|
baseUrl: config.baseUrl || DEFAULT_BASE_URL,
|
|
29
30
|
});
|
|
30
31
|
|
|
31
|
-
if (!apiKey
|
|
32
|
+
if (!apiKey) {
|
|
32
33
|
throw new GroqError(
|
|
33
34
|
GroqErrorType.INVALID_API_KEY,
|
|
34
|
-
"API key is required
|
|
35
|
+
"API key is required"
|
|
35
36
|
);
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
if (apiKey.length < MIN_API_KEY_LENGTH) {
|
|
40
|
+
throw new GroqError(
|
|
41
|
+
GroqErrorType.INVALID_API_KEY,
|
|
42
|
+
`API key must be at least ${MIN_API_KEY_LENGTH} characters`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!apiKey.startsWith(GROQ_KEY_PREFIX)) {
|
|
47
|
+
logger.warn("GroqClient", "API key does not start with expected prefix", {
|
|
48
|
+
prefix: GROQ_KEY_PREFIX,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
38
52
|
this.config = {
|
|
39
53
|
apiKey,
|
|
40
|
-
baseUrl: config.baseUrl
|
|
41
|
-
timeoutMs: config.timeoutMs
|
|
54
|
+
baseUrl: this.normalizeBaseUrl(config.baseUrl),
|
|
55
|
+
timeoutMs: this.validateTimeout(config.timeoutMs),
|
|
42
56
|
textModel: config.textModel,
|
|
43
57
|
};
|
|
44
58
|
this.initialized = true;
|
|
@@ -48,6 +62,28 @@ class GroqHttpClient {
|
|
|
48
62
|
});
|
|
49
63
|
}
|
|
50
64
|
|
|
65
|
+
private normalizeBaseUrl(baseUrl?: string): string {
|
|
66
|
+
if (!baseUrl) {
|
|
67
|
+
return DEFAULT_BASE_URL;
|
|
68
|
+
}
|
|
69
|
+
return baseUrl.replace(/\/+$/, ""); // Remove trailing slashes
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private validateTimeout(timeout?: number): number {
|
|
73
|
+
const DEFAULT = DEFAULT_TIMEOUT;
|
|
74
|
+
if (timeout === undefined || timeout === null) {
|
|
75
|
+
return DEFAULT;
|
|
76
|
+
}
|
|
77
|
+
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
78
|
+
logger.warn("GroqClient", "Invalid timeout, using default", {
|
|
79
|
+
provided: timeout,
|
|
80
|
+
default: DEFAULT,
|
|
81
|
+
});
|
|
82
|
+
return DEFAULT;
|
|
83
|
+
}
|
|
84
|
+
return Math.min(timeout, 300000); // Cap at 5 minutes
|
|
85
|
+
}
|
|
86
|
+
|
|
51
87
|
isInitialized(): boolean {
|
|
52
88
|
return this.initialized && this.config !== null;
|
|
53
89
|
}
|
|
@@ -95,7 +131,19 @@ class GroqHttpClient {
|
|
|
95
131
|
await this.handleErrorResponse(response);
|
|
96
132
|
}
|
|
97
133
|
|
|
98
|
-
|
|
134
|
+
const text = await response.text();
|
|
135
|
+
if (!text) {
|
|
136
|
+
throw new GroqError(GroqErrorType.SERVER_ERROR, "Empty response from server");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
return JSON.parse(text) as T;
|
|
141
|
+
} catch {
|
|
142
|
+
throw new GroqError(
|
|
143
|
+
GroqErrorType.SERVER_ERROR,
|
|
144
|
+
`Invalid JSON response: ${text.substring(0, 200)}`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
99
147
|
} catch (error) {
|
|
100
148
|
throw this.handleRequestError(error);
|
|
101
149
|
}
|
|
@@ -106,9 +154,17 @@ class GroqHttpClient {
|
|
|
106
154
|
const errorType = mapHttpStatusToErrorType(response.status);
|
|
107
155
|
|
|
108
156
|
try {
|
|
109
|
-
const
|
|
110
|
-
if (
|
|
111
|
-
|
|
157
|
+
const text = await response.text();
|
|
158
|
+
if (text) {
|
|
159
|
+
try {
|
|
160
|
+
const errorData = JSON.parse(text) as { error?: { message?: string } };
|
|
161
|
+
if (errorData.error?.message) {
|
|
162
|
+
errorMessage = errorData.error.message;
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
// Use text content if JSON parsing fails
|
|
166
|
+
errorMessage = text.substring(0, 500);
|
|
167
|
+
}
|
|
112
168
|
}
|
|
113
169
|
} catch {
|
|
114
170
|
// Use default message
|
|
@@ -122,9 +178,9 @@ class GroqHttpClient {
|
|
|
122
178
|
|
|
123
179
|
if (error instanceof Error) {
|
|
124
180
|
if (error.name === "AbortError") {
|
|
125
|
-
return new GroqError(GroqErrorType.ABORT_ERROR, "Request
|
|
181
|
+
return new GroqError(GroqErrorType.ABORT_ERROR, "Request timeout", error);
|
|
126
182
|
}
|
|
127
|
-
if (error.message.includes("network")) {
|
|
183
|
+
if (error.name === "TypeError" && error.message.includes("network")) {
|
|
128
184
|
return new GroqError(GroqErrorType.NETWORK_ERROR, "Network error", error);
|
|
129
185
|
}
|
|
130
186
|
}
|