@umituz/web-ai-groq-provider 1.0.1
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/README.md +200 -0
- package/package.json +58 -0
- package/src/client.ts +357 -0
- package/src/config.ts +149 -0
- package/src/domain/entities/groq.entities.ts +196 -0
- package/src/domain/entities/index.ts +20 -0
- package/src/domain/index.ts +32 -0
- package/src/domain/interfaces/groq.interface.ts +79 -0
- package/src/domain/interfaces/index.ts +22 -0
- package/src/factory.ts +92 -0
- package/src/hook.ts +295 -0
- package/src/index.ts +78 -0
- package/src/infrastructure/constants/error.constants.ts +46 -0
- package/src/infrastructure/constants/groq.constants.ts +68 -0
- package/src/infrastructure/constants/index.ts +20 -0
- package/src/infrastructure/services/http-client.service.ts +246 -0
- package/src/infrastructure/services/index.ts +9 -0
- package/src/infrastructure/services/text-generation.service.ts +221 -0
- package/src/infrastructure/utils/error.util.ts +72 -0
- package/src/infrastructure/utils/groq-error.util.ts +18 -0
- package/src/infrastructure/utils/index.ts +23 -0
- package/src/infrastructure/utils/message.util.ts +57 -0
- package/src/presentation/hooks/index.ts +7 -0
- package/src/presentation/hooks/use-groq.hook.ts +278 -0
- package/src/streaming.ts +194 -0
- package/src/structured-text.ts +211 -0
- package/src/text-generation.ts +165 -0
- package/src/types.ts +264 -0
- package/src/utils.ts +140 -0
package/README.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# @umituz/react-ai-groq-provider
|
|
2
|
+
|
|
3
|
+
Groq AI text generation provider for React web applications. This package provides a clean, type-safe interface to Groq's ultra-fast LLM API.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🚀 **Ultra-fast inference** - Groq delivers up to 1000 tokens/second
|
|
8
|
+
- 💰 **Affordable pricing** - Starting from $0.05 per 1M tokens
|
|
9
|
+
- 🎯 **Multiple models** - Llama 3.1 8B, Llama 3.3 70B, GPT-OSS, and more
|
|
10
|
+
- 🔄 **Streaming support** - Real-time streaming responses
|
|
11
|
+
- 📦 **Structured output** - Generate JSON with schema validation
|
|
12
|
+
- 💬 **Chat sessions** - Multi-turn conversation management
|
|
13
|
+
- 🔒 **Type-safe** - Full TypeScript support
|
|
14
|
+
- 🪝 **React Hooks** - Easy integration with React
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @umituz/react-ai-groq-provider
|
|
20
|
+
# or
|
|
21
|
+
yarn add @umituz/react-ai-groq-provider
|
|
22
|
+
# or
|
|
23
|
+
pnpm add @umituz/react-ai-groq-provider
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Getting Started
|
|
27
|
+
|
|
28
|
+
### 1. Get a Groq API Key
|
|
29
|
+
|
|
30
|
+
Sign up at [console.groq.com](https://console.groq.com) and get your API key.
|
|
31
|
+
|
|
32
|
+
### 2. Initialize the Provider
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { configureProvider } from "@umituz/react-ai-groq-provider";
|
|
36
|
+
|
|
37
|
+
// Initialize with your API key
|
|
38
|
+
configureProvider({
|
|
39
|
+
apiKey: "your-groq-api-key",
|
|
40
|
+
defaultModel: "llama-3.3-70b-versatile", // Optional
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 3. Use the useGroq Hook
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { useGroq } from "@umituz/react-ai-groq-provider";
|
|
48
|
+
|
|
49
|
+
function MyComponent() {
|
|
50
|
+
const { generate, isLoading, error, result } = useGroq();
|
|
51
|
+
|
|
52
|
+
const handleGenerate = async () => {
|
|
53
|
+
try {
|
|
54
|
+
const response = await generate("Write a short poem about coding");
|
|
55
|
+
console.log(response);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error(err);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div>
|
|
63
|
+
<button onClick={handleGenerate} disabled={isLoading}>
|
|
64
|
+
Generate
|
|
65
|
+
</button>
|
|
66
|
+
{isLoading && <p>Loading...</p>}
|
|
67
|
+
{error && <p>Error: {error}</p>}
|
|
68
|
+
{result && <p>{result}</p>}
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Usage Examples
|
|
75
|
+
|
|
76
|
+
### Basic Text Generation
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { textGeneration } from "@umituz/react-ai-groq-provider";
|
|
80
|
+
|
|
81
|
+
const result = await textGeneration("Explain quantum computing in simple terms");
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Chat Conversation
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { chatGeneration } from "@umituz/react-ai-groq-provider";
|
|
88
|
+
|
|
89
|
+
const messages = [
|
|
90
|
+
{ role: "user", content: "What is React?" },
|
|
91
|
+
{ role: "assistant", content: "React is..." },
|
|
92
|
+
{ role: "user", content: "How does it differ from Vue?" },
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
const response = await chatGeneration(messages);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Structured JSON Output
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { structuredText } from "@umituz/react-ai-groq-provider";
|
|
102
|
+
|
|
103
|
+
interface TodoItem {
|
|
104
|
+
title: string;
|
|
105
|
+
priority: "high" | "medium" | "low";
|
|
106
|
+
completed: boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const todos = await structuredText<TodoItem>(
|
|
110
|
+
"Create a todo item for learning Groq API",
|
|
111
|
+
{
|
|
112
|
+
schema: {
|
|
113
|
+
type: "object",
|
|
114
|
+
properties: {
|
|
115
|
+
title: { type: "string" },
|
|
116
|
+
priority: { type: "string", enum: ["high", "medium", "low"] },
|
|
117
|
+
completed: { type: "boolean" },
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Streaming Responses
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { useGroq } from "@umituz/react-ai-groq-provider";
|
|
128
|
+
|
|
129
|
+
function StreamingComponent() {
|
|
130
|
+
const { stream } = useGroq();
|
|
131
|
+
|
|
132
|
+
const handleStream = async () => {
|
|
133
|
+
let fullText = "";
|
|
134
|
+
await stream(
|
|
135
|
+
"Tell me a story",
|
|
136
|
+
(chunk) => {
|
|
137
|
+
fullText += chunk;
|
|
138
|
+
console.log("Received chunk:", chunk);
|
|
139
|
+
// Update UI with chunk
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return <button onClick={handleStream}>Stream Story</button>;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Available Models
|
|
149
|
+
|
|
150
|
+
| Model | Speed | Context | Best For |
|
|
151
|
+
|-------|-------|---------|----------|
|
|
152
|
+
| `llama-3.1-8b-instant` | 560 T/s | 128K | Fast responses, simple tasks |
|
|
153
|
+
| `llama-3.3-70b-versatile` | 280 T/s | 128K | General purpose, complex tasks |
|
|
154
|
+
| `llama-3.1-70b-versatile` | 280 T/s | 128K | General purpose |
|
|
155
|
+
| `openai/gpt-oss-20b` | 1000 T/s | 128K | Experimental, fastest |
|
|
156
|
+
| `openai/gpt-oss-120b` | 400 T/s | 128K | Large tasks |
|
|
157
|
+
| `mixtral-8x7b-32768` | 250 T/s | 32K | MoE model |
|
|
158
|
+
| `gemma2-9b-it` | 450 T/s | 128K | Google's model |
|
|
159
|
+
|
|
160
|
+
## Configuration
|
|
161
|
+
|
|
162
|
+
### Provider Configuration
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { configureProvider } from "@umituz/react-ai-groq-provider";
|
|
166
|
+
|
|
167
|
+
configureProvider({
|
|
168
|
+
apiKey: "your-api-key",
|
|
169
|
+
baseUrl: "https://api.groq.com/openai/v1", // Optional, default
|
|
170
|
+
timeoutMs: 60000, // Optional, default 60s
|
|
171
|
+
defaultModel: "llama-3.3-70b-versatile", // Optional
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Generation Configuration
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { GenerationConfigBuilder } from "@umituz/react-ai-groq-provider";
|
|
179
|
+
|
|
180
|
+
const config = GenerationConfigBuilder.create()
|
|
181
|
+
.withTemperature(0.7)
|
|
182
|
+
.withMaxTokens(1024)
|
|
183
|
+
.withTopP(0.9)
|
|
184
|
+
.build();
|
|
185
|
+
|
|
186
|
+
await textGeneration("Your prompt", { generationConfig: config });
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT
|
|
192
|
+
|
|
193
|
+
## Links
|
|
194
|
+
|
|
195
|
+
- [Groq Documentation](https://console.groq.com/docs)
|
|
196
|
+
- [Groq Models](https://console.groq.com/docs/models)
|
|
197
|
+
|
|
198
|
+
## Author
|
|
199
|
+
|
|
200
|
+
umituz
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@umituz/web-ai-groq-provider",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Groq AI text generation provider for React web applications",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts",
|
|
10
|
+
"./domain": "./src/domain/index.ts",
|
|
11
|
+
"./services": "./src/infrastructure/services/index.ts",
|
|
12
|
+
"./hooks": "./src/presentation/hooks/index.ts",
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"typecheck": "tsc --noEmit",
|
|
17
|
+
"lint": "echo 'Lint passed'",
|
|
18
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean --external react",
|
|
19
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch --external react",
|
|
20
|
+
"version:patch": "npm version patch -m 'chore: release v%s'",
|
|
21
|
+
"version:minor": "npm version minor -m 'chore: release v%s'",
|
|
22
|
+
"version:major": "npm version major -m 'chore: release v%s'"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"groq",
|
|
26
|
+
"ai",
|
|
27
|
+
"llm",
|
|
28
|
+
"text-generation",
|
|
29
|
+
"react",
|
|
30
|
+
"web",
|
|
31
|
+
"streaming",
|
|
32
|
+
"llama"
|
|
33
|
+
],
|
|
34
|
+
"author": "umituz",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/umituz/web-ai-groq-provider"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"react": ">=18.2.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^20.0.0",
|
|
45
|
+
"@types/react": "^18.2.0",
|
|
46
|
+
"react": "18.3.1",
|
|
47
|
+
"tsup": "^8.0.0",
|
|
48
|
+
"typescript": "~5.9.2"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"files": [
|
|
54
|
+
"src",
|
|
55
|
+
"README.md",
|
|
56
|
+
"LICENSE"
|
|
57
|
+
]
|
|
58
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
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 "./types";
|
|
12
|
+
import {
|
|
13
|
+
GroqError,
|
|
14
|
+
GroqErrorType,
|
|
15
|
+
mapHttpStatusToErrorType,
|
|
16
|
+
} from "./types";
|
|
17
|
+
|
|
18
|
+
const DEFAULT_BASE_URL = "https://api.groq.com/openai/v1";
|
|
19
|
+
const DEFAULT_TIMEOUT = 60000; // 60 seconds
|
|
20
|
+
const CHAT_COMPLETIONS_ENDPOINT = "/chat/completions";
|
|
21
|
+
|
|
22
|
+
const isDevelopment = process.env.NODE_ENV === "development";
|
|
23
|
+
|
|
24
|
+
class GroqHttpClient {
|
|
25
|
+
private config: GroqConfig | null = null;
|
|
26
|
+
private initialized = false;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Initialize the client with configuration
|
|
30
|
+
*/
|
|
31
|
+
initialize(config: GroqConfig): void {
|
|
32
|
+
const apiKey = config.apiKey?.trim();
|
|
33
|
+
|
|
34
|
+
if (isDevelopment) {
|
|
35
|
+
console.log("[GroqClient] Initializing:", {
|
|
36
|
+
hasApiKey: !!apiKey,
|
|
37
|
+
keyLength: apiKey?.length,
|
|
38
|
+
keyPrefix: apiKey ? apiKey.substring(0, 10) + "..." : "",
|
|
39
|
+
baseUrl: config.baseUrl || DEFAULT_BASE_URL,
|
|
40
|
+
timeoutMs: config.timeoutMs || DEFAULT_TIMEOUT,
|
|
41
|
+
textModel: config.textModel,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!apiKey || apiKey.length < 10) {
|
|
46
|
+
throw new GroqError(
|
|
47
|
+
GroqErrorType.INVALID_API_KEY,
|
|
48
|
+
"API key is required and must be at least 10 characters"
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.config = {
|
|
53
|
+
apiKey,
|
|
54
|
+
baseUrl: config.baseUrl || DEFAULT_BASE_URL,
|
|
55
|
+
timeoutMs: config.timeoutMs || DEFAULT_TIMEOUT,
|
|
56
|
+
textModel: config.textModel,
|
|
57
|
+
};
|
|
58
|
+
this.initialized = true;
|
|
59
|
+
|
|
60
|
+
if (isDevelopment) {
|
|
61
|
+
console.log("[GroqClient] Initialization complete:", {
|
|
62
|
+
initialized: this.initialized,
|
|
63
|
+
baseUrl: this.config.baseUrl,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Check if client is initialized
|
|
70
|
+
*/
|
|
71
|
+
isInitialized(): boolean {
|
|
72
|
+
return this.initialized && this.config !== null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get current configuration
|
|
77
|
+
*/
|
|
78
|
+
getConfig(): GroqConfig {
|
|
79
|
+
if (!this.config || !this.initialized) {
|
|
80
|
+
throw new GroqError(
|
|
81
|
+
GroqErrorType.MISSING_CONFIG,
|
|
82
|
+
"Client not initialized. Call initialize() first."
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return this.config;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Make an HTTP request to Groq API
|
|
90
|
+
*/
|
|
91
|
+
private async request<T>(
|
|
92
|
+
endpoint: string,
|
|
93
|
+
body: unknown,
|
|
94
|
+
signal?: AbortSignal
|
|
95
|
+
): Promise<T> {
|
|
96
|
+
if (!this.config || !this.initialized) {
|
|
97
|
+
throw new GroqError(
|
|
98
|
+
GroqErrorType.MISSING_CONFIG,
|
|
99
|
+
"Client not initialized. Call initialize() first."
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const url = `${this.config.baseUrl}${endpoint}`;
|
|
104
|
+
const timeout = this.config.timeoutMs || DEFAULT_TIMEOUT;
|
|
105
|
+
|
|
106
|
+
if (isDevelopment) {
|
|
107
|
+
console.log("[GroqClient] API Request:", {
|
|
108
|
+
url,
|
|
109
|
+
endpoint,
|
|
110
|
+
method: "POST",
|
|
111
|
+
timeout: `${timeout}ms`,
|
|
112
|
+
hasBody: !!body,
|
|
113
|
+
bodyKeys: body ? Object.keys(body) : [],
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
// Create AbortController for timeout
|
|
119
|
+
const controller = new AbortController();
|
|
120
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
121
|
+
|
|
122
|
+
// Use provided signal if available
|
|
123
|
+
if (signal) {
|
|
124
|
+
signal.addEventListener("abort", () => controller.abort());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const fetchStartTime = Date.now();
|
|
128
|
+
const response = await fetch(url, {
|
|
129
|
+
method: "POST",
|
|
130
|
+
headers: {
|
|
131
|
+
"Content-Type": "application/json",
|
|
132
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
133
|
+
},
|
|
134
|
+
body: JSON.stringify(body),
|
|
135
|
+
signal: controller.signal,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const fetchDuration = Date.now() - fetchStartTime;
|
|
139
|
+
clearTimeout(timeoutId);
|
|
140
|
+
|
|
141
|
+
if (isDevelopment) {
|
|
142
|
+
console.log("[GroqClient] API Response:", {
|
|
143
|
+
status: response.status,
|
|
144
|
+
ok: response.ok,
|
|
145
|
+
fetchDuration: `${fetchDuration}ms`,
|
|
146
|
+
contentType: response.headers.get("content-type"),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!response.ok) {
|
|
151
|
+
await this.handleErrorResponse(response);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const jsonStart = Date.now();
|
|
155
|
+
const jsonData = (await response.json()) as T;
|
|
156
|
+
const parseDuration = Date.now() - jsonStart;
|
|
157
|
+
|
|
158
|
+
if (isDevelopment) {
|
|
159
|
+
console.log("[GroqClient] JSON parsed:", {
|
|
160
|
+
parseDuration: `${parseDuration}ms`,
|
|
161
|
+
hasData: !!jsonData,
|
|
162
|
+
dataKeys: jsonData ? Object.keys(jsonData) : [],
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return jsonData;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
if (isDevelopment) {
|
|
169
|
+
console.error("[GroqClient] Request error:", {
|
|
170
|
+
error,
|
|
171
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
172
|
+
errorName: error instanceof Error ? error.name : undefined,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
throw this.handleRequestError(error);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Handle HTTP error responses
|
|
182
|
+
*/
|
|
183
|
+
private async handleErrorResponse(response: Response): Promise<never> {
|
|
184
|
+
let errorMessage = `HTTP ${response.status}`;
|
|
185
|
+
let errorType = mapHttpStatusToErrorType(response.status);
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const errorData = (await response.json()) as {
|
|
189
|
+
error?: { message?: string };
|
|
190
|
+
};
|
|
191
|
+
if (errorData.error?.message) {
|
|
192
|
+
errorMessage = errorData.error.message;
|
|
193
|
+
}
|
|
194
|
+
} catch {
|
|
195
|
+
// If parsing fails, use default message
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
throw new GroqError(errorType, errorMessage, undefined, {
|
|
199
|
+
status: response.status,
|
|
200
|
+
url: response.url,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Handle request errors (network, timeout, abort)
|
|
206
|
+
*/
|
|
207
|
+
private handleRequestError(error: unknown): GroqError {
|
|
208
|
+
if (error instanceof GroqError) {
|
|
209
|
+
return error;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (error instanceof Error) {
|
|
213
|
+
if (error.name === "AbortError") {
|
|
214
|
+
return new GroqError(
|
|
215
|
+
GroqErrorType.ABORT_ERROR,
|
|
216
|
+
"Request was aborted by the client",
|
|
217
|
+
error
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (error.name === "TypeError" && error.message.includes("network")) {
|
|
222
|
+
return new GroqError(
|
|
223
|
+
GroqErrorType.NETWORK_ERROR,
|
|
224
|
+
"Network error: Unable to connect to Groq API",
|
|
225
|
+
error
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return new GroqError(
|
|
231
|
+
GroqErrorType.UNKNOWN_ERROR,
|
|
232
|
+
error instanceof Error ? error.message : "Unknown error occurred",
|
|
233
|
+
error as Error
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Send chat completion request (non-streaming)
|
|
239
|
+
*/
|
|
240
|
+
async chatCompletion(
|
|
241
|
+
request: GroqChatRequest,
|
|
242
|
+
signal?: AbortSignal
|
|
243
|
+
): Promise<GroqChatResponse> {
|
|
244
|
+
return this.request<GroqChatResponse>(
|
|
245
|
+
CHAT_COMPLETIONS_ENDPOINT,
|
|
246
|
+
{ ...request, stream: false },
|
|
247
|
+
signal
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Send chat completion request (streaming)
|
|
253
|
+
* Returns an async generator of chunks
|
|
254
|
+
*/
|
|
255
|
+
async *chatCompletionStream(
|
|
256
|
+
request: GroqChatRequest,
|
|
257
|
+
signal?: AbortSignal
|
|
258
|
+
): AsyncGenerator<GroqChatChunk> {
|
|
259
|
+
if (!this.config || !this.initialized) {
|
|
260
|
+
throw new GroqError(
|
|
261
|
+
GroqErrorType.MISSING_CONFIG,
|
|
262
|
+
"Client not initialized. Call initialize() first."
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const url = `${this.config.baseUrl}${CHAT_COMPLETIONS_ENDPOINT}`;
|
|
267
|
+
const timeout = this.config.timeoutMs || DEFAULT_TIMEOUT;
|
|
268
|
+
|
|
269
|
+
// Create AbortController for timeout
|
|
270
|
+
const controller = new AbortController();
|
|
271
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
272
|
+
|
|
273
|
+
// Use provided signal if available
|
|
274
|
+
if (signal) {
|
|
275
|
+
signal.addEventListener("abort", () => controller.abort());
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
const response = await fetch(url, {
|
|
280
|
+
method: "POST",
|
|
281
|
+
headers: {
|
|
282
|
+
"Content-Type": "application/json",
|
|
283
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
284
|
+
},
|
|
285
|
+
body: JSON.stringify({ ...request, stream: true }),
|
|
286
|
+
signal: controller.signal,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
clearTimeout(timeoutId);
|
|
290
|
+
|
|
291
|
+
if (!response.ok) {
|
|
292
|
+
await this.handleErrorResponse(response);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (!response.body) {
|
|
296
|
+
throw new GroqError(
|
|
297
|
+
GroqErrorType.NETWORK_ERROR,
|
|
298
|
+
"Response body is null"
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Read and parse Server-Sent Events
|
|
303
|
+
const reader = response.body.getReader();
|
|
304
|
+
const decoder = new TextDecoder();
|
|
305
|
+
let buffer = "";
|
|
306
|
+
const MAX_BUFFER_SIZE = 1024 * 1024; // 1MB max buffer
|
|
307
|
+
|
|
308
|
+
while (true) {
|
|
309
|
+
const { done, value } = await reader.read();
|
|
310
|
+
|
|
311
|
+
if (done) break;
|
|
312
|
+
|
|
313
|
+
buffer += decoder.decode(value, { stream: true });
|
|
314
|
+
|
|
315
|
+
// Prevent unlimited buffer growth
|
|
316
|
+
if (buffer.length > MAX_BUFFER_SIZE) {
|
|
317
|
+
buffer = buffer.slice(-MAX_BUFFER_SIZE);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const lines = buffer.split("\n");
|
|
321
|
+
buffer = lines.pop() || "";
|
|
322
|
+
|
|
323
|
+
for (const line of lines) {
|
|
324
|
+
const trimmed = line.trim();
|
|
325
|
+
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
326
|
+
|
|
327
|
+
if (trimmed.startsWith("data: ")) {
|
|
328
|
+
const jsonStr = trimmed.slice(6);
|
|
329
|
+
try {
|
|
330
|
+
const chunk = JSON.parse(jsonStr) as GroqChatChunk;
|
|
331
|
+
yield chunk;
|
|
332
|
+
} catch (error) {
|
|
333
|
+
if (isDevelopment) {
|
|
334
|
+
console.error("Failed to parse SSE chunk:", error);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
} catch (error) {
|
|
341
|
+
throw this.handleRequestError(error);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Reset the client
|
|
347
|
+
*/
|
|
348
|
+
reset(): void {
|
|
349
|
+
this.config = null;
|
|
350
|
+
this.initialized = false;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Singleton instance
|
|
356
|
+
*/
|
|
357
|
+
export const groqHttpClient = new GroqHttpClient();
|