@umituz/web-ai-groq-provider 1.1.13 → 2.0.0
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 +154 -72
- package/dist/domains/chat/index.cjs +539 -0
- package/dist/domains/chat/index.cjs.map +1 -0
- package/dist/domains/chat/index.d.cts +322 -0
- package/dist/domains/chat/index.d.ts +322 -0
- package/dist/domains/chat/index.js +531 -0
- package/dist/domains/chat/index.js.map +1 -0
- package/dist/domains/groq/index.cjs +1499 -0
- package/dist/domains/groq/index.cjs.map +1 -0
- package/dist/domains/groq/index.d.cts +561 -0
- package/dist/domains/groq/index.d.ts +561 -0
- package/dist/domains/groq/index.js +1453 -0
- package/dist/domains/groq/index.js.map +1 -0
- package/dist/index.cjs +1993 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +53 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +1937 -0
- package/dist/index.js.map +1 -0
- package/dist/text-generation.service-BP4Q_SH0.d.cts +252 -0
- package/dist/text-generation.service-BP4Q_SH0.d.ts +252 -0
- package/package.json +25 -11
- package/src/domains/chat/entities/chat.entities.ts +0 -113
- package/src/domains/chat/entities/index.ts +0 -14
- package/src/domains/chat/hooks/index.ts +0 -5
- package/src/domains/chat/hooks/use-chat.hook.ts +0 -259
- package/src/domains/chat/index.ts +0 -32
- package/src/domains/chat/interfaces/chat.interface.ts +0 -80
- package/src/domains/chat/interfaces/index.ts +0 -10
- package/src/domains/chat/services/chat.service.ts +0 -195
- package/src/domains/chat/services/index.ts +0 -5
- package/src/domains/chat/utils/message-formatter.ts +0 -49
- package/src/domains/groq/constants/error.constants.ts +0 -46
- package/src/domains/groq/constants/groq.constants.ts +0 -68
- package/src/domains/groq/constants/index.ts +0 -20
- package/src/domains/groq/entities/groq.entities.ts +0 -196
- package/src/domains/groq/entities/index.ts +0 -20
- package/src/domains/groq/hooks/index.ts +0 -7
- package/src/domains/groq/hooks/use-groq.hook.ts +0 -232
- package/src/domains/groq/index.ts +0 -72
- package/src/domains/groq/interfaces/groq.interface.ts +0 -77
- package/src/domains/groq/interfaces/index.ts +0 -22
- package/src/domains/groq/services/http-client.service.ts +0 -265
- package/src/domains/groq/services/index.ts +0 -9
- package/src/domains/groq/services/text-generation.service.ts +0 -238
- package/src/domains/groq/utils/cache-manager.util.ts +0 -215
- package/src/domains/groq/utils/debounce.util.ts +0 -268
- package/src/domains/groq/utils/error.util.ts +0 -57
- package/src/domains/groq/utils/groq-error.util.ts +0 -18
- package/src/domains/groq/utils/index.ts +0 -37
- package/src/domains/groq/utils/message.util.ts +0 -57
- package/src/domains/groq/utils/request-deduplicator.util.ts +0 -123
- package/src/domains/groq/utils/request-queue.util.ts +0 -178
- package/src/domains/groq/utils/retry.util.ts +0 -207
- package/src/index.ts +0 -15
|
@@ -0,0 +1,1499 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
|
|
5
|
+
// src/domains/groq/utils/groq-error.ts
|
|
6
|
+
var GroqError = class extends Error {
|
|
7
|
+
type;
|
|
8
|
+
cause;
|
|
9
|
+
metadata;
|
|
10
|
+
constructor(type, message, cause, metadata = {}) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "GroqError";
|
|
13
|
+
this.type = type;
|
|
14
|
+
this.cause = cause;
|
|
15
|
+
this.metadata = metadata;
|
|
16
|
+
}
|
|
17
|
+
toString() {
|
|
18
|
+
const statusPart = typeof this.metadata.status === "number" ? ` (HTTP ${this.metadata.status})` : "";
|
|
19
|
+
return `${this.name} [${this.type}]: ${this.message}${statusPart}`;
|
|
20
|
+
}
|
|
21
|
+
toJSON() {
|
|
22
|
+
return {
|
|
23
|
+
name: this.name,
|
|
24
|
+
type: this.type,
|
|
25
|
+
message: this.message,
|
|
26
|
+
metadata: this.metadata
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// src/domains/groq/constants/error-types.ts
|
|
32
|
+
var GroqErrorType = /* @__PURE__ */ ((GroqErrorType2) => {
|
|
33
|
+
GroqErrorType2["INVALID_API_KEY"] = "INVALID_API_KEY";
|
|
34
|
+
GroqErrorType2["MISSING_CONFIG"] = "MISSING_CONFIG";
|
|
35
|
+
GroqErrorType2["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
36
|
+
GroqErrorType2["ABORT_ERROR"] = "ABORT_ERROR";
|
|
37
|
+
GroqErrorType2["RATE_LIMIT_ERROR"] = "RATE_LIMIT_ERROR";
|
|
38
|
+
GroqErrorType2["SERVER_ERROR"] = "SERVER_ERROR";
|
|
39
|
+
GroqErrorType2["CLIENT_ERROR"] = "CLIENT_ERROR";
|
|
40
|
+
GroqErrorType2["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
|
|
41
|
+
return GroqErrorType2;
|
|
42
|
+
})(GroqErrorType || {});
|
|
43
|
+
var USER_FRIENDLY_ERROR_MESSAGES = {
|
|
44
|
+
["INVALID_API_KEY" /* INVALID_API_KEY */]: "Please check your Groq API key and try again.",
|
|
45
|
+
["MISSING_CONFIG" /* MISSING_CONFIG */]: "The provider is not configured. Please call configureProvider first.",
|
|
46
|
+
["NETWORK_ERROR" /* NETWORK_ERROR */]: "Network error. Please check your connection and try again.",
|
|
47
|
+
["ABORT_ERROR" /* ABORT_ERROR */]: "Request was cancelled.",
|
|
48
|
+
["RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */]: "You've reached the rate limit. Please wait a moment and try again.",
|
|
49
|
+
["SERVER_ERROR" /* SERVER_ERROR */]: "Groq service is temporarily unavailable. Please try again later.",
|
|
50
|
+
["CLIENT_ERROR" /* CLIENT_ERROR */]: "The request was invalid. Please check your input and try again.",
|
|
51
|
+
["UNKNOWN_ERROR" /* UNKNOWN_ERROR */]: "An unexpected error occurred. Please try again."
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// src/domains/groq/constants/groq.constants.ts
|
|
55
|
+
var GROQ_MODELS = {
|
|
56
|
+
/** Llama 3.1 8B - Fast and efficient (560 T/s) */
|
|
57
|
+
LLAMA_3_1_8B_INSTANT: "llama-3.1-8b-instant",
|
|
58
|
+
/** Llama 3.3 70B - Versatile and powerful (280 T/s) */
|
|
59
|
+
LLAMA_3_3_70B_VERSATILE: "llama-3.3-70b-versatile",
|
|
60
|
+
/** Llama 3.1 70B - Versatile (280 T/s) */
|
|
61
|
+
LLAMA_3_1_70B_VERSATILE: "llama-3.1-70b-versatile",
|
|
62
|
+
/** GPT-OSS 20B - Experimental (1000 T/s) */
|
|
63
|
+
GPT_OSS_20B: "openai/gpt-oss-20b",
|
|
64
|
+
/** GPT-OSS 120B - Large experimental model */
|
|
65
|
+
GPT_OSS_120B: "openai/gpt-oss-120b",
|
|
66
|
+
/** Mixtral 8x7b - MoE model */
|
|
67
|
+
MIXTRAL_8X7B: "mixtral-8x7b-32768",
|
|
68
|
+
/** Gemma 2 9B - Google's model */
|
|
69
|
+
GEMMA_2_9B: "gemma2-9b-it",
|
|
70
|
+
/** Llama 4 Scout 17B - New model (30K T/s) */
|
|
71
|
+
LLAMA_4_SCOUT_17B: "meta-llama/llama-4-scout-17b-16e-instruct",
|
|
72
|
+
/** Kimi K2 - Moonshot AI model */
|
|
73
|
+
KIMI_K2_INSTRUCT: "moonshotai/kimi-k2-instruct",
|
|
74
|
+
/** Qwen 3 32B - Alibaba's model */
|
|
75
|
+
QWEN3_32B: "qwen/qwen3-32b"
|
|
76
|
+
};
|
|
77
|
+
var DEFAULT_MODELS = {
|
|
78
|
+
TEXT: GROQ_MODELS.LLAMA_3_1_8B_INSTANT,
|
|
79
|
+
FAST: GROQ_MODELS.LLAMA_3_1_8B_INSTANT,
|
|
80
|
+
EXPERIMENTAL: GROQ_MODELS.GPT_OSS_20B
|
|
81
|
+
};
|
|
82
|
+
var DEFAULT_GENERATION_CONFIG = {
|
|
83
|
+
temperature: 0.7,
|
|
84
|
+
maxTokens: 1024,
|
|
85
|
+
topP: 1
|
|
86
|
+
};
|
|
87
|
+
var API_ENDPOINTS = {
|
|
88
|
+
CHAT: "/chat/completions"
|
|
89
|
+
};
|
|
90
|
+
var DEFAULT_BASE_URL = "https://api.groq.com/openai/v1";
|
|
91
|
+
var TIMEOUTS = {
|
|
92
|
+
DEFAULT: 3e4,
|
|
93
|
+
// 30 seconds
|
|
94
|
+
STREAMING: 6e4
|
|
95
|
+
// 60 seconds
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// src/domains/groq/constants/identifier.constants.ts
|
|
99
|
+
var CACHE_KEY_TYPE_COMPLETION = "completion";
|
|
100
|
+
var CACHE_KEY_TYPE_CHAT = "chat";
|
|
101
|
+
var QUEUE_PRIORITY_USER_REQUEST = 10;
|
|
102
|
+
var DEFAULT_RETRY_MAX_ATTEMPTS = 3;
|
|
103
|
+
var DEFAULT_RETRY_INITIAL_DELAY_MS = 1e3;
|
|
104
|
+
var DEFAULT_RETRY_MAX_DELAY_MS = 1e4;
|
|
105
|
+
var DEFAULT_RETRY_BACKOFF_MULTIPLIER = 2;
|
|
106
|
+
var DEFAULT_RETRY_JITTER_FACTOR = 0.1;
|
|
107
|
+
|
|
108
|
+
// src/domains/groq/calculations/lru-key-selector.ts
|
|
109
|
+
function selectLeastRecentlyUsedKey(entries) {
|
|
110
|
+
let oldestKey = null;
|
|
111
|
+
let oldestAccess = Number.POSITIVE_INFINITY;
|
|
112
|
+
for (const [key, entry] of entries.entries()) {
|
|
113
|
+
if (entry.lastAccess < oldestAccess) {
|
|
114
|
+
oldestAccess = entry.lastAccess;
|
|
115
|
+
oldestKey = key;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return oldestKey;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/domains/groq/calculations/cache-hit-rate.ts
|
|
122
|
+
function calculateCacheHitRate(entries) {
|
|
123
|
+
if (entries.length === 0) return 0;
|
|
124
|
+
const totalAccess = entries.reduce((sum, e) => sum + e.accessCount, 0);
|
|
125
|
+
return totalAccess / entries.length;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/domains/groq/builders/cache-key-builder.ts
|
|
129
|
+
function buildCacheKey(params) {
|
|
130
|
+
const sortedKeys = Object.keys(params).sort();
|
|
131
|
+
const sortedParams = {};
|
|
132
|
+
for (const key of sortedKeys) {
|
|
133
|
+
sortedParams[key] = params[key];
|
|
134
|
+
}
|
|
135
|
+
return JSON.stringify(sortedParams);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/domains/groq/utils/cache-manager.ts
|
|
139
|
+
var DEFAULT_CACHE_MAX_SIZE = 100;
|
|
140
|
+
var DEFAULT_CACHE_TTL_MS = 3e5;
|
|
141
|
+
var CacheManager = class {
|
|
142
|
+
cache = /* @__PURE__ */ new Map();
|
|
143
|
+
maxSize;
|
|
144
|
+
ttl;
|
|
145
|
+
constructor(options = {}) {
|
|
146
|
+
this.maxSize = options.maxSize ?? DEFAULT_CACHE_MAX_SIZE;
|
|
147
|
+
this.ttl = options.ttl ?? DEFAULT_CACHE_TTL_MS;
|
|
148
|
+
}
|
|
149
|
+
get(key) {
|
|
150
|
+
const entry = this.cache.get(key);
|
|
151
|
+
if (!entry) return null;
|
|
152
|
+
if (this.isExpired(entry)) {
|
|
153
|
+
this.cache.delete(key);
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
entry.accessCount += 1;
|
|
157
|
+
entry.lastAccess = Date.now();
|
|
158
|
+
return entry.value;
|
|
159
|
+
}
|
|
160
|
+
set(key, value) {
|
|
161
|
+
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
|
|
162
|
+
const lruKey = selectLeastRecentlyUsedKey(this.cache);
|
|
163
|
+
if (lruKey !== null) this.cache.delete(lruKey);
|
|
164
|
+
}
|
|
165
|
+
this.cache.set(key, {
|
|
166
|
+
value,
|
|
167
|
+
timestamp: Date.now(),
|
|
168
|
+
accessCount: 1,
|
|
169
|
+
lastAccess: Date.now()
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
has(key) {
|
|
173
|
+
const entry = this.cache.get(key);
|
|
174
|
+
return entry !== void 0 && !this.isExpired(entry);
|
|
175
|
+
}
|
|
176
|
+
delete(key) {
|
|
177
|
+
return this.cache.delete(key);
|
|
178
|
+
}
|
|
179
|
+
clear() {
|
|
180
|
+
this.cache.clear();
|
|
181
|
+
}
|
|
182
|
+
/** Build a stable cache key from a parameter object. */
|
|
183
|
+
generateKey(params) {
|
|
184
|
+
return buildCacheKey(params);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Dynamic cache statistics. All numbers are computed at call time
|
|
188
|
+
* from the current state; nothing is hardcoded.
|
|
189
|
+
*/
|
|
190
|
+
getStats() {
|
|
191
|
+
const now = Date.now();
|
|
192
|
+
const entries = Array.from(this.cache.entries()).map(([key, entry]) => ({
|
|
193
|
+
key,
|
|
194
|
+
accessCount: entry.accessCount,
|
|
195
|
+
ageMs: now - entry.timestamp
|
|
196
|
+
}));
|
|
197
|
+
const hitRate = calculateCacheHitRate(
|
|
198
|
+
Array.from(this.cache.values()).map((e) => ({ accessCount: e.accessCount }))
|
|
199
|
+
);
|
|
200
|
+
return {
|
|
201
|
+
size: this.cache.size,
|
|
202
|
+
maxSize: this.maxSize,
|
|
203
|
+
hitRate,
|
|
204
|
+
entries
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
isExpired(entry) {
|
|
208
|
+
return Date.now() - entry.timestamp > this.ttl;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
var cacheManager = new CacheManager();
|
|
212
|
+
|
|
213
|
+
// src/domains/groq/builders/groq-request.builder.ts
|
|
214
|
+
var GroqChatRequestBuilder = class _GroqChatRequestBuilder {
|
|
215
|
+
model;
|
|
216
|
+
messages = [];
|
|
217
|
+
generationConfig = {};
|
|
218
|
+
streamEnabled = false;
|
|
219
|
+
constructor(model) {
|
|
220
|
+
this.model = model;
|
|
221
|
+
}
|
|
222
|
+
static for(model) {
|
|
223
|
+
return new _GroqChatRequestBuilder(model);
|
|
224
|
+
}
|
|
225
|
+
withMessages(messages) {
|
|
226
|
+
this.messages = [...messages];
|
|
227
|
+
return this;
|
|
228
|
+
}
|
|
229
|
+
withConfig(config) {
|
|
230
|
+
this.generationConfig = config ? { ...config } : {};
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
withStream(stream) {
|
|
234
|
+
this.streamEnabled = stream;
|
|
235
|
+
return this;
|
|
236
|
+
}
|
|
237
|
+
build() {
|
|
238
|
+
const cfg = this.generationConfig;
|
|
239
|
+
const request = {
|
|
240
|
+
model: this.model,
|
|
241
|
+
messages: this.messages,
|
|
242
|
+
temperature: cfg.temperature ?? DEFAULT_GENERATION_CONFIG.temperature,
|
|
243
|
+
max_tokens: cfg.maxTokens ?? DEFAULT_GENERATION_CONFIG.maxTokens,
|
|
244
|
+
top_p: cfg.topP ?? DEFAULT_GENERATION_CONFIG.topP
|
|
245
|
+
};
|
|
246
|
+
if (cfg.n !== void 0) request.n = cfg.n;
|
|
247
|
+
if (cfg.stop !== void 0) request.stop = cfg.stop;
|
|
248
|
+
if (cfg.frequencyPenalty !== void 0) request.frequency_penalty = cfg.frequencyPenalty;
|
|
249
|
+
if (cfg.presencePenalty !== void 0) request.presence_penalty = cfg.presencePenalty;
|
|
250
|
+
if (this.streamEnabled) request.stream = true;
|
|
251
|
+
return request;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// src/domains/groq/builders/structured-prompt.builder.ts
|
|
256
|
+
var StructuredPromptBuilder = class _StructuredPromptBuilder {
|
|
257
|
+
schema;
|
|
258
|
+
request;
|
|
259
|
+
constructor(request) {
|
|
260
|
+
this.request = request;
|
|
261
|
+
}
|
|
262
|
+
static for(request) {
|
|
263
|
+
return new _StructuredPromptBuilder(request);
|
|
264
|
+
}
|
|
265
|
+
withSchema(schema) {
|
|
266
|
+
this.schema = schema;
|
|
267
|
+
return this;
|
|
268
|
+
}
|
|
269
|
+
build() {
|
|
270
|
+
const schemaSection = this.schema ? `Expected JSON schema:
|
|
271
|
+
${JSON.stringify(this.schema, null, 2)}
|
|
272
|
+
|
|
273
|
+
` : "";
|
|
274
|
+
return "Please generate a JSON response for the following request.\n\n" + schemaSection + `Request:
|
|
275
|
+
${this.request}
|
|
276
|
+
|
|
277
|
+
Respond only with valid JSON, formatted as:
|
|
278
|
+
\`\`\`json
|
|
279
|
+
{...}
|
|
280
|
+
\`\`\``;
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// src/domains/groq/constants/protocol.constants.ts
|
|
285
|
+
var HTTP_HEADERS = {
|
|
286
|
+
CONTENT_TYPE: "Content-Type",
|
|
287
|
+
AUTHORIZATION: "Authorization",
|
|
288
|
+
APPLICATION_JSON: "application/json",
|
|
289
|
+
BEARER_PREFIX: "Bearer "
|
|
290
|
+
};
|
|
291
|
+
var SSE_PREFIX = "data: ";
|
|
292
|
+
var SSE_DONE_MARKER = "[DONE]";
|
|
293
|
+
var JSON_CODE_BLOCK_PATTERN = /```json\n([\s\S]*?)\n```/;
|
|
294
|
+
var JSON_OBJECT_PATTERN = /\{[\s\S]*\}/;
|
|
295
|
+
var CHAT_COMPLETIONS_PATH = "/chat/completions";
|
|
296
|
+
|
|
297
|
+
// src/domains/groq/parsers/sse-line-extractor.ts
|
|
298
|
+
function isSseDataLine(line) {
|
|
299
|
+
return line.startsWith(SSE_PREFIX);
|
|
300
|
+
}
|
|
301
|
+
function extractSseDataPayload(line) {
|
|
302
|
+
return line.slice(SSE_PREFIX.length);
|
|
303
|
+
}
|
|
304
|
+
function isSseDoneMarker(payload) {
|
|
305
|
+
return payload === SSE_DONE_MARKER;
|
|
306
|
+
}
|
|
307
|
+
function splitSseLines(rawChunk) {
|
|
308
|
+
return rawChunk.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
309
|
+
}
|
|
310
|
+
function extractContentFromSsePayload(payload) {
|
|
311
|
+
try {
|
|
312
|
+
const parsed = JSON.parse(payload);
|
|
313
|
+
return parsed.choices?.[0]?.delta?.content ?? null;
|
|
314
|
+
} catch {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function normalizeSseChunk(rawChunk) {
|
|
319
|
+
const lines = splitSseLines(rawChunk);
|
|
320
|
+
const result = [];
|
|
321
|
+
for (const line of lines) {
|
|
322
|
+
if (!isSseDataLine(line)) continue;
|
|
323
|
+
const payload = extractSseDataPayload(line);
|
|
324
|
+
result.push({ payload, isDone: isSseDoneMarker(payload) });
|
|
325
|
+
}
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/domains/groq/parsers/streaming-response.parser.ts
|
|
330
|
+
var StreamingResponseParser = class {
|
|
331
|
+
constructor(callbacks) {
|
|
332
|
+
this.callbacks = callbacks;
|
|
333
|
+
}
|
|
334
|
+
decoder = new TextDecoder();
|
|
335
|
+
async parse(stream) {
|
|
336
|
+
const reader = stream.getReader();
|
|
337
|
+
const contentChunks = [];
|
|
338
|
+
try {
|
|
339
|
+
while (true) {
|
|
340
|
+
const { done, value } = await reader.read();
|
|
341
|
+
if (done) break;
|
|
342
|
+
const chunk = this.decoder.decode(value, { stream: true });
|
|
343
|
+
const normalized = normalizeSseChunk(chunk);
|
|
344
|
+
for (const item of normalized) {
|
|
345
|
+
if (item.isDone) continue;
|
|
346
|
+
const content = extractContentFromSsePayload(item.payload);
|
|
347
|
+
if (content !== null) {
|
|
348
|
+
contentChunks.push(content);
|
|
349
|
+
this.callbacks.onChunk(content);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
} catch (error) {
|
|
354
|
+
if (error instanceof GroqError) throw error;
|
|
355
|
+
throw new GroqError(
|
|
356
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
357
|
+
"Stream was interrupted",
|
|
358
|
+
error instanceof Error ? error : void 0
|
|
359
|
+
);
|
|
360
|
+
} finally {
|
|
361
|
+
reader.releaseLock();
|
|
362
|
+
}
|
|
363
|
+
return contentChunks.join("");
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// src/domains/groq/parsers/json-content-extractor.ts
|
|
368
|
+
function extractJsonFromCodeFence(content) {
|
|
369
|
+
const match = content.match(JSON_CODE_BLOCK_PATTERN);
|
|
370
|
+
if (match && match[1]) return match[1];
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
function extractJsonObject(content) {
|
|
374
|
+
const match = content.match(JSON_OBJECT_PATTERN);
|
|
375
|
+
if (match && match[0]) return match[0];
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
function extractJsonCandidate(content) {
|
|
379
|
+
return extractJsonFromCodeFence(content) ?? extractJsonObject(content) ?? content;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/domains/groq/parsers/json-response.parser.ts
|
|
383
|
+
var JsonResponseParser = class {
|
|
384
|
+
parse(rawContent) {
|
|
385
|
+
const candidate = extractJsonCandidate(rawContent);
|
|
386
|
+
try {
|
|
387
|
+
return JSON.parse(candidate);
|
|
388
|
+
} catch (error) {
|
|
389
|
+
throw new GroqError(
|
|
390
|
+
"UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
391
|
+
"Failed to parse structured response as JSON",
|
|
392
|
+
error instanceof Error ? error : void 0
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// src/domains/groq/utils/message.util.ts
|
|
399
|
+
var createUserMessage = (content) => ({
|
|
400
|
+
role: "user",
|
|
401
|
+
content
|
|
402
|
+
});
|
|
403
|
+
var createAssistantMessage = (content) => ({
|
|
404
|
+
role: "assistant",
|
|
405
|
+
content
|
|
406
|
+
});
|
|
407
|
+
var createSystemMessage = (content) => ({
|
|
408
|
+
role: "system",
|
|
409
|
+
content
|
|
410
|
+
});
|
|
411
|
+
var createMessage = (role, content) => ({
|
|
412
|
+
role,
|
|
413
|
+
content
|
|
414
|
+
});
|
|
415
|
+
var promptToMessages = (prompt) => [
|
|
416
|
+
createUserMessage(prompt)
|
|
417
|
+
];
|
|
418
|
+
var extractTextFromMessages = (messages) => messages.map((m) => m.content).join("\n\n");
|
|
419
|
+
var formatMessagesForDisplay = (messages) => messages.map((m) => `[${m.role.toUpperCase()}]: ${m.content}`).join("\n\n---\n\n");
|
|
420
|
+
|
|
421
|
+
// src/domains/groq/services/text-generation.service.ts
|
|
422
|
+
var STRUCTURED_OUTPUT_TEMPERATURE = 0.1;
|
|
423
|
+
var TextGenerationService = class {
|
|
424
|
+
constructor(httpClient, cache = new CacheManager()) {
|
|
425
|
+
this.httpClient = httpClient;
|
|
426
|
+
this.cache = cache;
|
|
427
|
+
}
|
|
428
|
+
jsonParser = new JsonResponseParser();
|
|
429
|
+
generateCompletion(prompt, options = {}) {
|
|
430
|
+
const model = options.model ?? DEFAULT_MODELS.TEXT;
|
|
431
|
+
return this.executeWithCache(
|
|
432
|
+
{
|
|
433
|
+
type: CACHE_KEY_TYPE_COMPLETION,
|
|
434
|
+
model,
|
|
435
|
+
prompt,
|
|
436
|
+
config: options.generationConfig
|
|
437
|
+
},
|
|
438
|
+
async () => {
|
|
439
|
+
const request = GroqChatRequestBuilder.for(model).withMessages([createUserMessage(prompt)]).withConfig(options.generationConfig).build();
|
|
440
|
+
const response = await this.httpClient.postChatCompletion(request);
|
|
441
|
+
return this.extractContent(response.choices[0]?.message?.content);
|
|
442
|
+
}
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
generateChatCompletion(messages, options = {}) {
|
|
446
|
+
const model = options.model ?? DEFAULT_MODELS.TEXT;
|
|
447
|
+
return this.executeWithCache(
|
|
448
|
+
{
|
|
449
|
+
type: CACHE_KEY_TYPE_CHAT,
|
|
450
|
+
model,
|
|
451
|
+
messages: [...messages],
|
|
452
|
+
config: options.generationConfig
|
|
453
|
+
},
|
|
454
|
+
async () => {
|
|
455
|
+
const request = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig(options.generationConfig).build();
|
|
456
|
+
const response = await this.httpClient.postChatCompletion(request);
|
|
457
|
+
return this.extractContent(response.choices[0]?.message?.content);
|
|
458
|
+
}
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
generateStructured(prompt, options = {}) {
|
|
462
|
+
const model = options.model ?? DEFAULT_MODELS.TEXT;
|
|
463
|
+
const jsonPrompt = StructuredPromptBuilder.for(prompt).withSchema(options.schema).build();
|
|
464
|
+
const request = GroqChatRequestBuilder.for(model).withMessages([createUserMessage(jsonPrompt)]).withConfig({
|
|
465
|
+
...options.generationConfig,
|
|
466
|
+
temperature: STRUCTURED_OUTPUT_TEMPERATURE
|
|
467
|
+
}).build();
|
|
468
|
+
return this.httpClient.postChatCompletion(request).then((response) => {
|
|
469
|
+
const content = this.extractContent(response.choices[0]?.message?.content);
|
|
470
|
+
return this.jsonParser.parse(content);
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
streamCompletion(prompt, callbacks, options = {}) {
|
|
474
|
+
return this.streamChatCompletion([createUserMessage(prompt)], callbacks, options);
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Stream a chat completion given an existing message history.
|
|
478
|
+
* Resolves with the accumulated full text once the stream completes.
|
|
479
|
+
*
|
|
480
|
+
* @param messages - The full conversation history.
|
|
481
|
+
* @param callbacks - Streaming callbacks (`onChunk`, `onComplete`, `onError`).
|
|
482
|
+
* @param options - Generation options.
|
|
483
|
+
* @returns The full accumulated text.
|
|
484
|
+
*/
|
|
485
|
+
async streamChatCompletion(messages, callbacks, options = {}) {
|
|
486
|
+
const model = options.model ?? DEFAULT_MODELS.TEXT;
|
|
487
|
+
const request = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig(options.generationConfig).withStream(true).build();
|
|
488
|
+
const parser = new StreamingResponseParser({
|
|
489
|
+
onChunk: (chunk) => callbacks.onChunk?.(chunk)
|
|
490
|
+
});
|
|
491
|
+
try {
|
|
492
|
+
const stream = await this.httpClient.streamChatCompletion(request);
|
|
493
|
+
const fullText = await parser.parse(stream);
|
|
494
|
+
callbacks.onComplete?.(fullText);
|
|
495
|
+
return fullText;
|
|
496
|
+
} catch (error) {
|
|
497
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
498
|
+
callbacks.onError?.(err);
|
|
499
|
+
throw err;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Clear the response cache (used by long-running services to free memory).
|
|
504
|
+
*/
|
|
505
|
+
clearCache() {
|
|
506
|
+
this.cache.clear();
|
|
507
|
+
}
|
|
508
|
+
async executeWithCache(params, factory) {
|
|
509
|
+
const key = this.cache.generateKey(params);
|
|
510
|
+
const cached = this.cache.get(key);
|
|
511
|
+
if (cached !== null) {
|
|
512
|
+
return cached;
|
|
513
|
+
}
|
|
514
|
+
const result = await factory();
|
|
515
|
+
this.cache.set(key, result);
|
|
516
|
+
return result;
|
|
517
|
+
}
|
|
518
|
+
extractContent(content) {
|
|
519
|
+
if (!content) {
|
|
520
|
+
throw new GroqError(
|
|
521
|
+
"UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
522
|
+
"No content generated from Groq API"
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
return content;
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
// src/domains/groq/validators/api-key-validator.ts
|
|
530
|
+
var DEFAULT_API_KEY_RULE = {
|
|
531
|
+
minLength: 10
|
|
532
|
+
};
|
|
533
|
+
function validateApiKey(apiKey, rule = DEFAULT_API_KEY_RULE) {
|
|
534
|
+
if (!apiKey) {
|
|
535
|
+
return { valid: false, reason: "API key is required" };
|
|
536
|
+
}
|
|
537
|
+
const trimmed = apiKey.trim();
|
|
538
|
+
if (trimmed.length === 0) {
|
|
539
|
+
return { valid: false, reason: "API key cannot be empty" };
|
|
540
|
+
}
|
|
541
|
+
if (trimmed.length < rule.minLength) {
|
|
542
|
+
return {
|
|
543
|
+
valid: false,
|
|
544
|
+
reason: `API key must be at least ${rule.minLength} characters`
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
return { valid: true };
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/domains/groq/clients/groq-config-store.ts
|
|
551
|
+
var GroqConfigStore = class {
|
|
552
|
+
config = null;
|
|
553
|
+
/**
|
|
554
|
+
* Validate and persist the given config. Cancels any prior state
|
|
555
|
+
* by overwriting (callers that need atomicity should use `reset()` first).
|
|
556
|
+
*
|
|
557
|
+
* @throws GroqError with type `INVALID_API_KEY` if validation fails.
|
|
558
|
+
*/
|
|
559
|
+
initialize(config) {
|
|
560
|
+
const validation = validateApiKey(config.apiKey);
|
|
561
|
+
if (!validation.valid) {
|
|
562
|
+
throw new GroqError("INVALID_API_KEY" /* INVALID_API_KEY */, validation.reason ?? "Invalid API key");
|
|
563
|
+
}
|
|
564
|
+
const normalized = {
|
|
565
|
+
apiKey: config.apiKey.trim(),
|
|
566
|
+
baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
|
|
567
|
+
timeoutMs: config.timeoutMs ?? TIMEOUTS.DEFAULT,
|
|
568
|
+
...config.textModel !== void 0 ? { textModel: config.textModel } : {}
|
|
569
|
+
};
|
|
570
|
+
this.config = normalized;
|
|
571
|
+
return normalized;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Returns the active config or throws if not initialized.
|
|
575
|
+
*
|
|
576
|
+
* @throws GroqError with type `MISSING_CONFIG` when not initialized.
|
|
577
|
+
*/
|
|
578
|
+
get() {
|
|
579
|
+
if (!this.config) {
|
|
580
|
+
throw new GroqError(
|
|
581
|
+
"MISSING_CONFIG" /* MISSING_CONFIG */,
|
|
582
|
+
"Client not initialized. Call configureProvider first."
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
return this.config;
|
|
586
|
+
}
|
|
587
|
+
getOrNull() {
|
|
588
|
+
return this.config;
|
|
589
|
+
}
|
|
590
|
+
isInitialized() {
|
|
591
|
+
return this.config !== null;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Replace the API key.
|
|
595
|
+
*
|
|
596
|
+
* @throws GroqError with type `MISSING_CONFIG` if not initialized,
|
|
597
|
+
* or `INVALID_API_KEY` if the new key fails validation.
|
|
598
|
+
*/
|
|
599
|
+
setApiKey(apiKey) {
|
|
600
|
+
const current = this.get();
|
|
601
|
+
const validation = validateApiKey(apiKey);
|
|
602
|
+
if (!validation.valid) {
|
|
603
|
+
throw new GroqError("INVALID_API_KEY" /* INVALID_API_KEY */, validation.reason ?? "Invalid API key");
|
|
604
|
+
}
|
|
605
|
+
this.config = { ...current, apiKey: apiKey.trim() };
|
|
606
|
+
}
|
|
607
|
+
reset() {
|
|
608
|
+
this.config = null;
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
// src/domains/groq/validators/http-status-classifier.ts
|
|
613
|
+
var DEFAULT_HTTP_STATUS_RULE = {
|
|
614
|
+
unauthorizedStatuses: /* @__PURE__ */ new Set([401, 403]),
|
|
615
|
+
rateLimitedStatus: 429,
|
|
616
|
+
serverErrorThreshold: 500,
|
|
617
|
+
clientErrorThreshold: 400
|
|
618
|
+
};
|
|
619
|
+
function classifyHttpStatus(status, rule = DEFAULT_HTTP_STATUS_RULE) {
|
|
620
|
+
if (rule.unauthorizedStatuses.has(status)) return "INVALID_API_KEY" /* INVALID_API_KEY */;
|
|
621
|
+
if (status === rule.rateLimitedStatus) return "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */;
|
|
622
|
+
if (status >= rule.serverErrorThreshold) return "SERVER_ERROR" /* SERVER_ERROR */;
|
|
623
|
+
if (status >= rule.clientErrorThreshold) return "CLIENT_ERROR" /* CLIENT_ERROR */;
|
|
624
|
+
return "UNKNOWN_ERROR" /* UNKNOWN_ERROR */;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// src/domains/groq/extractors/response-error-message-extractor.ts
|
|
628
|
+
function extractErrorMessageFromJsonBody(body) {
|
|
629
|
+
if (!body || typeof body !== "object") return null;
|
|
630
|
+
const candidate = body;
|
|
631
|
+
const message = candidate.error?.message;
|
|
632
|
+
if (typeof message === "string" && message.length > 0) return message;
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
function buildHttpStatusFallback(status, statusText) {
|
|
636
|
+
const trimmedStatusText = statusText.trim();
|
|
637
|
+
if (trimmedStatusText.length > 0) {
|
|
638
|
+
return `HTTP ${status}: ${trimmedStatusText}`;
|
|
639
|
+
}
|
|
640
|
+
return `HTTP ${status}`;
|
|
641
|
+
}
|
|
642
|
+
async function extractErrorMessageFromResponse(response) {
|
|
643
|
+
try {
|
|
644
|
+
const body = await response.json();
|
|
645
|
+
const fromBody = extractErrorMessageFromJsonBody(body);
|
|
646
|
+
if (fromBody) return fromBody;
|
|
647
|
+
} catch {
|
|
648
|
+
}
|
|
649
|
+
return buildHttpStatusFallback(response.status, response.statusText);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/domains/groq/clients/groq-http-transport.ts
|
|
653
|
+
var GroqHttpTransport = class {
|
|
654
|
+
constructor(configStore) {
|
|
655
|
+
this.configStore = configStore;
|
|
656
|
+
}
|
|
657
|
+
pendingAbortControllers = /* @__PURE__ */ new Set();
|
|
658
|
+
cancelPendingRequests() {
|
|
659
|
+
for (const controller of this.pendingAbortControllers) {
|
|
660
|
+
controller.abort();
|
|
661
|
+
}
|
|
662
|
+
this.pendingAbortControllers.clear();
|
|
663
|
+
}
|
|
664
|
+
async postChatCompletion(request) {
|
|
665
|
+
return this.request(CHAT_COMPLETIONS_PATH, {
|
|
666
|
+
...request,
|
|
667
|
+
stream: false
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
async streamChatCompletion(request) {
|
|
671
|
+
const config = this.configStore.get();
|
|
672
|
+
const url = `${config.baseUrl}${CHAT_COMPLETIONS_PATH}`;
|
|
673
|
+
const timeout = config.timeoutMs ?? TIMEOUTS.STREAMING;
|
|
674
|
+
const controller = new AbortController();
|
|
675
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
676
|
+
this.pendingAbortControllers.add(controller);
|
|
677
|
+
try {
|
|
678
|
+
const response = await fetch(url, {
|
|
679
|
+
method: "POST",
|
|
680
|
+
headers: this.buildHeaders(config.apiKey),
|
|
681
|
+
body: JSON.stringify({ ...request, stream: true }),
|
|
682
|
+
signal: controller.signal
|
|
683
|
+
});
|
|
684
|
+
if (!response.ok) await this.throwFromErrorResponse(response);
|
|
685
|
+
if (!response.body) {
|
|
686
|
+
throw new GroqError("NETWORK_ERROR" /* NETWORK_ERROR */, "Response body is null");
|
|
687
|
+
}
|
|
688
|
+
return response.body;
|
|
689
|
+
} catch (error) {
|
|
690
|
+
throw this.normalizeError(error);
|
|
691
|
+
} finally {
|
|
692
|
+
clearTimeout(timeoutId);
|
|
693
|
+
this.pendingAbortControllers.delete(controller);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
async request(endpoint, body) {
|
|
697
|
+
const config = this.configStore.get();
|
|
698
|
+
const url = `${config.baseUrl}${endpoint}`;
|
|
699
|
+
const timeout = config.timeoutMs ?? TIMEOUTS.DEFAULT;
|
|
700
|
+
const controller = new AbortController();
|
|
701
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
702
|
+
this.pendingAbortControllers.add(controller);
|
|
703
|
+
try {
|
|
704
|
+
const response = await fetch(url, {
|
|
705
|
+
method: "POST",
|
|
706
|
+
headers: this.buildHeaders(config.apiKey),
|
|
707
|
+
body: JSON.stringify(body),
|
|
708
|
+
signal: controller.signal
|
|
709
|
+
});
|
|
710
|
+
if (!response.ok) await this.throwFromErrorResponse(response);
|
|
711
|
+
return await response.json();
|
|
712
|
+
} catch (error) {
|
|
713
|
+
throw this.normalizeError(error);
|
|
714
|
+
} finally {
|
|
715
|
+
clearTimeout(timeoutId);
|
|
716
|
+
this.pendingAbortControllers.delete(controller);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
buildHeaders(apiKey) {
|
|
720
|
+
return {
|
|
721
|
+
[HTTP_HEADERS.CONTENT_TYPE]: HTTP_HEADERS.APPLICATION_JSON,
|
|
722
|
+
[HTTP_HEADERS.AUTHORIZATION]: `${HTTP_HEADERS.BEARER_PREFIX}${apiKey}`
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
async throwFromErrorResponse(response) {
|
|
726
|
+
const errorMessage = await extractErrorMessageFromResponse(response);
|
|
727
|
+
const metadata = {
|
|
728
|
+
status: response.status,
|
|
729
|
+
url: response.url
|
|
730
|
+
};
|
|
731
|
+
throw new GroqError(
|
|
732
|
+
classifyHttpStatus(response.status),
|
|
733
|
+
errorMessage,
|
|
734
|
+
void 0,
|
|
735
|
+
metadata
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
normalizeError(error) {
|
|
739
|
+
if (error instanceof GroqError) return error;
|
|
740
|
+
if (error instanceof Error) {
|
|
741
|
+
if (error.name === "AbortError") {
|
|
742
|
+
return new GroqError(
|
|
743
|
+
"ABORT_ERROR" /* ABORT_ERROR */,
|
|
744
|
+
"Request was aborted by the client",
|
|
745
|
+
error
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
if (error.name === "TypeError" && error.message.toLowerCase().includes("network")) {
|
|
749
|
+
return new GroqError(
|
|
750
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
751
|
+
"Network error: Unable to connect to Groq API",
|
|
752
|
+
error
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return new GroqError(
|
|
757
|
+
"UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
758
|
+
error instanceof Error ? error.message : "Unknown error occurred",
|
|
759
|
+
error instanceof Error ? error : void 0
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
// src/domains/groq/utils/request-deduplicator.util.ts
|
|
765
|
+
var DEFAULT_DEDUPE_TTL_MS = 3e4;
|
|
766
|
+
var RequestDeduplicator = class {
|
|
767
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
768
|
+
ttl;
|
|
769
|
+
constructor(ttl = DEFAULT_DEDUPE_TTL_MS) {
|
|
770
|
+
this.ttl = ttl;
|
|
771
|
+
}
|
|
772
|
+
execute(key, requestFn) {
|
|
773
|
+
this.cleanup();
|
|
774
|
+
const existing = this.pendingRequests.get(key);
|
|
775
|
+
if (existing) {
|
|
776
|
+
return existing.promise;
|
|
777
|
+
}
|
|
778
|
+
const promise = requestFn();
|
|
779
|
+
this.pendingRequests.set(key, {
|
|
780
|
+
promise,
|
|
781
|
+
timestamp: Date.now()
|
|
782
|
+
});
|
|
783
|
+
promise.finally(() => {
|
|
784
|
+
this.pendingRequests.delete(key);
|
|
785
|
+
});
|
|
786
|
+
return promise;
|
|
787
|
+
}
|
|
788
|
+
cancelAll() {
|
|
789
|
+
this.pendingRequests.clear();
|
|
790
|
+
}
|
|
791
|
+
cleanup() {
|
|
792
|
+
const now = Date.now();
|
|
793
|
+
for (const [key, request] of this.pendingRequests.entries()) {
|
|
794
|
+
if (now - request.timestamp > this.ttl) {
|
|
795
|
+
this.pendingRequests.delete(key);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
var requestDeduplicator = new RequestDeduplicator();
|
|
801
|
+
|
|
802
|
+
// src/domains/groq/utils/request-queue.util.ts
|
|
803
|
+
var DEFAULT_MAX_CONCURRENT = 3;
|
|
804
|
+
var DEFAULT_PRIORITY = 0;
|
|
805
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
806
|
+
var RequestQueue = class {
|
|
807
|
+
queue = [];
|
|
808
|
+
activeRequests = /* @__PURE__ */ new Set();
|
|
809
|
+
maxConcurrent;
|
|
810
|
+
defaultPriority;
|
|
811
|
+
requestTimeout;
|
|
812
|
+
requestIdCounter = 0;
|
|
813
|
+
constructor(options = {}) {
|
|
814
|
+
this.maxConcurrent = options.maxConcurrent ?? DEFAULT_MAX_CONCURRENT;
|
|
815
|
+
this.defaultPriority = options.defaultPriority ?? DEFAULT_PRIORITY;
|
|
816
|
+
this.requestTimeout = options.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
817
|
+
}
|
|
818
|
+
add(requestFn, priority) {
|
|
819
|
+
return new Promise((resolve, reject) => {
|
|
820
|
+
const id = `req-${++this.requestIdCounter}`;
|
|
821
|
+
const queuedRequest = {
|
|
822
|
+
id,
|
|
823
|
+
priority: priority ?? this.defaultPriority,
|
|
824
|
+
requestFn,
|
|
825
|
+
resolve,
|
|
826
|
+
reject,
|
|
827
|
+
timestamp: Date.now()
|
|
828
|
+
};
|
|
829
|
+
this.queue.push(queuedRequest);
|
|
830
|
+
this.sortQueue();
|
|
831
|
+
this.process();
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
cancelAll() {
|
|
835
|
+
for (const request of this.queue) {
|
|
836
|
+
request.reject(new Error("Request was cancelled"));
|
|
837
|
+
}
|
|
838
|
+
this.queue = [];
|
|
839
|
+
}
|
|
840
|
+
sortQueue() {
|
|
841
|
+
this.queue.sort((a, b) => {
|
|
842
|
+
if (a.priority !== b.priority) {
|
|
843
|
+
return b.priority - a.priority;
|
|
844
|
+
}
|
|
845
|
+
return a.timestamp - b.timestamp;
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
process() {
|
|
849
|
+
while (this.queue.length > 0 && this.activeRequests.size < this.maxConcurrent) {
|
|
850
|
+
const request = this.queue.shift();
|
|
851
|
+
if (!request) break;
|
|
852
|
+
this.executeRequest(request);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
executeRequest(request) {
|
|
856
|
+
this.activeRequests.add(request.id);
|
|
857
|
+
const timeoutId = setTimeout(() => {
|
|
858
|
+
this.activeRequests.delete(request.id);
|
|
859
|
+
request.reject(new Error(`Request ${request.id} timed out`));
|
|
860
|
+
this.process();
|
|
861
|
+
}, this.requestTimeout);
|
|
862
|
+
request.requestFn().then((result) => {
|
|
863
|
+
clearTimeout(timeoutId);
|
|
864
|
+
request.resolve(result);
|
|
865
|
+
}).catch((error) => {
|
|
866
|
+
clearTimeout(timeoutId);
|
|
867
|
+
request.reject(error);
|
|
868
|
+
}).finally(() => {
|
|
869
|
+
this.activeRequests.delete(request.id);
|
|
870
|
+
this.process();
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
var requestQueue = new RequestQueue();
|
|
875
|
+
|
|
876
|
+
// src/domains/groq/calculations/exponential-backoff.ts
|
|
877
|
+
function calculateExponentialBackoff(params) {
|
|
878
|
+
const {
|
|
879
|
+
attempt,
|
|
880
|
+
initialDelay,
|
|
881
|
+
maxDelay,
|
|
882
|
+
backoffMultiplier,
|
|
883
|
+
jitterFactor,
|
|
884
|
+
random = Math.random
|
|
885
|
+
} = params;
|
|
886
|
+
const base = initialDelay * Math.pow(backoffMultiplier, Math.max(0, attempt - 1));
|
|
887
|
+
const capped = Math.min(base, maxDelay);
|
|
888
|
+
const jitterMultiplier = random() * 2 - 1;
|
|
889
|
+
const jitter = capped * jitterFactor * jitterMultiplier;
|
|
890
|
+
return Math.max(0, capped + jitter);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// src/domains/groq/validators/error-classifier.ts
|
|
894
|
+
var DEFAULT_RETRYABLE_TYPES = /* @__PURE__ */ new Set([
|
|
895
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
896
|
+
"RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */,
|
|
897
|
+
"SERVER_ERROR" /* SERVER_ERROR */
|
|
898
|
+
]);
|
|
899
|
+
var DEFAULT_AUTH_TYPES = /* @__PURE__ */ new Set([
|
|
900
|
+
"INVALID_API_KEY" /* INVALID_API_KEY */
|
|
901
|
+
]);
|
|
902
|
+
var DEFAULT_ERROR_CLASSIFICATION_RULE = {
|
|
903
|
+
retryable: DEFAULT_RETRYABLE_TYPES,
|
|
904
|
+
auth: DEFAULT_AUTH_TYPES
|
|
905
|
+
};
|
|
906
|
+
function isRetryableErrorType(type, rule = DEFAULT_ERROR_CLASSIFICATION_RULE) {
|
|
907
|
+
return rule.retryable.has(type);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// src/domains/groq/utils/retry.ts
|
|
911
|
+
var RETRYABLE_MESSAGE_KEYWORDS = [
|
|
912
|
+
"network",
|
|
913
|
+
"timeout",
|
|
914
|
+
"econnrefused",
|
|
915
|
+
"enotfound",
|
|
916
|
+
"etimedout"
|
|
917
|
+
];
|
|
918
|
+
var RetryManager = class {
|
|
919
|
+
defaults;
|
|
920
|
+
constructor() {
|
|
921
|
+
this.defaults = {
|
|
922
|
+
maxAttempts: DEFAULT_RETRY_MAX_ATTEMPTS,
|
|
923
|
+
initialDelay: DEFAULT_RETRY_INITIAL_DELAY_MS,
|
|
924
|
+
maxDelay: DEFAULT_RETRY_MAX_DELAY_MS,
|
|
925
|
+
backoffMultiplier: DEFAULT_RETRY_BACKOFF_MULTIPLIER,
|
|
926
|
+
jitterFactor: DEFAULT_RETRY_JITTER_FACTOR
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
async execute(fn, options = {}) {
|
|
930
|
+
const maxAttempts = options.maxAttempts ?? this.defaults.maxAttempts;
|
|
931
|
+
let lastError;
|
|
932
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
933
|
+
try {
|
|
934
|
+
return await fn();
|
|
935
|
+
} catch (error) {
|
|
936
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
937
|
+
if (attempt >= maxAttempts || !this.shouldRetry(lastError, options)) {
|
|
938
|
+
throw lastError;
|
|
939
|
+
}
|
|
940
|
+
const delay = calculateExponentialBackoff({
|
|
941
|
+
attempt,
|
|
942
|
+
initialDelay: options.initialDelay ?? this.defaults.initialDelay,
|
|
943
|
+
maxDelay: options.maxDelay ?? this.defaults.maxDelay,
|
|
944
|
+
backoffMultiplier: options.backoffMultiplier ?? this.defaults.backoffMultiplier,
|
|
945
|
+
jitterFactor: options.jitterFactor ?? this.defaults.jitterFactor
|
|
946
|
+
});
|
|
947
|
+
await this.delay(delay);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
throw lastError ?? new Error("Retry loop exited without result");
|
|
951
|
+
}
|
|
952
|
+
shouldRetry(error, options) {
|
|
953
|
+
if (options.shouldRetry) return options.shouldRetry(error);
|
|
954
|
+
if (error instanceof GroqError) {
|
|
955
|
+
return isRetryableErrorType(error.type, DEFAULT_ERROR_CLASSIFICATION_RULE);
|
|
956
|
+
}
|
|
957
|
+
const lower = error.message.toLowerCase();
|
|
958
|
+
return RETRYABLE_MESSAGE_KEYWORDS.some((kw) => lower.includes(kw));
|
|
959
|
+
}
|
|
960
|
+
delay(ms) {
|
|
961
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
var retryManager = new RetryManager();
|
|
965
|
+
async function withRetry(fn, options) {
|
|
966
|
+
return retryManager.execute(fn, options);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// src/domains/groq/strategies/retry-executor.strategy.ts
|
|
970
|
+
var RetryExecutor = class {
|
|
971
|
+
constructor(retryManager2, options) {
|
|
972
|
+
this.retryManager = retryManager2;
|
|
973
|
+
this.options = options;
|
|
974
|
+
}
|
|
975
|
+
execute(fn, ...args) {
|
|
976
|
+
return this.retryManager.execute(() => fn(...args), this.options);
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
// src/domains/groq/strategies/queue-executor.strategy.ts
|
|
981
|
+
var QueueExecutor = class {
|
|
982
|
+
constructor(queue, priority) {
|
|
983
|
+
this.queue = queue;
|
|
984
|
+
this.priority = priority;
|
|
985
|
+
}
|
|
986
|
+
execute(fn, ...args) {
|
|
987
|
+
return this.queue.add(() => fn(...args), this.priority);
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
|
|
991
|
+
// src/domains/groq/strategies/dedupe-executor.strategy.ts
|
|
992
|
+
var DedupeExecutor = class {
|
|
993
|
+
constructor(deduplicator, options) {
|
|
994
|
+
this.deduplicator = deduplicator;
|
|
995
|
+
this.options = options;
|
|
996
|
+
}
|
|
997
|
+
execute(fn, ...args) {
|
|
998
|
+
const key = this.options.generateKey(...args);
|
|
999
|
+
return this.deduplicator.execute(key, () => fn(...args));
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
|
|
1003
|
+
// src/domains/groq/strategies/composite-executor.strategy.ts
|
|
1004
|
+
var CompositeExecutor = class {
|
|
1005
|
+
executors;
|
|
1006
|
+
constructor(executors) {
|
|
1007
|
+
this.executors = executors;
|
|
1008
|
+
}
|
|
1009
|
+
execute(fn, ...args) {
|
|
1010
|
+
const composed = this.compress(fn);
|
|
1011
|
+
return composed(...args);
|
|
1012
|
+
}
|
|
1013
|
+
compress(fn) {
|
|
1014
|
+
return this.executors.reduceRight(
|
|
1015
|
+
(innerFn, executor) => (...a) => executor.execute(innerFn, ...a),
|
|
1016
|
+
fn
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
// src/domains/groq/clients/groq-http-client.ts
|
|
1022
|
+
var GroqHttpClient = class {
|
|
1023
|
+
configStore;
|
|
1024
|
+
transport;
|
|
1025
|
+
executor;
|
|
1026
|
+
constructor(deps = {}) {
|
|
1027
|
+
this.configStore = deps.configStore ?? new GroqConfigStore();
|
|
1028
|
+
this.transport = deps.transport ?? new GroqHttpTransport(this.configStore);
|
|
1029
|
+
const retryManager2 = deps.retryManager ?? new RetryManager();
|
|
1030
|
+
const queue = deps.queue ?? new RequestQueue();
|
|
1031
|
+
const deduplicator = deps.deduplicator ?? new RequestDeduplicator();
|
|
1032
|
+
this.executor = new CompositeExecutor([
|
|
1033
|
+
new DedupeExecutor(deduplicator, {
|
|
1034
|
+
generateKey: (request) => deduplicatorKey(
|
|
1035
|
+
request,
|
|
1036
|
+
this.configStore.get().apiKey
|
|
1037
|
+
)
|
|
1038
|
+
}),
|
|
1039
|
+
new RetryExecutor(retryManager2),
|
|
1040
|
+
new QueueExecutor(
|
|
1041
|
+
queue,
|
|
1042
|
+
QUEUE_PRIORITY_USER_REQUEST
|
|
1043
|
+
)
|
|
1044
|
+
]);
|
|
1045
|
+
}
|
|
1046
|
+
initialize(config) {
|
|
1047
|
+
this.configStore.initialize(config);
|
|
1048
|
+
}
|
|
1049
|
+
cancelPendingRequests() {
|
|
1050
|
+
this.transport.cancelPendingRequests();
|
|
1051
|
+
}
|
|
1052
|
+
isInitialized() {
|
|
1053
|
+
return this.configStore.isInitialized();
|
|
1054
|
+
}
|
|
1055
|
+
getApiKey() {
|
|
1056
|
+
return this.configStore.get().apiKey;
|
|
1057
|
+
}
|
|
1058
|
+
setApiKey(apiKey) {
|
|
1059
|
+
this.configStore.setApiKey(apiKey);
|
|
1060
|
+
}
|
|
1061
|
+
postChatCompletion(request) {
|
|
1062
|
+
return this.executor.execute(
|
|
1063
|
+
(req) => this.transport.postChatCompletion(req),
|
|
1064
|
+
request
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
streamChatCompletion(request) {
|
|
1068
|
+
return this.transport.streamChatCompletion(request);
|
|
1069
|
+
}
|
|
1070
|
+
reset() {
|
|
1071
|
+
this.transport.cancelPendingRequests();
|
|
1072
|
+
this.configStore.reset();
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
function deduplicatorKey(request, apiKey) {
|
|
1076
|
+
const { model, messages, stream: _stream, ...rest } = request;
|
|
1077
|
+
const stable = JSON.stringify({
|
|
1078
|
+
apiKey,
|
|
1079
|
+
model,
|
|
1080
|
+
messages: messages.map((m) => ({ role: m.role, content: m.content })),
|
|
1081
|
+
...rest
|
|
1082
|
+
});
|
|
1083
|
+
return stable;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// src/domains/groq/resolvers/user-friendly-error-resolver.ts
|
|
1087
|
+
var KEYWORD_FALLBACK_RULES = [
|
|
1088
|
+
{ keywords: ["api key", "unauthorized"], type: "INVALID_API_KEY" /* INVALID_API_KEY */ },
|
|
1089
|
+
{ keywords: ["rate limit"], type: "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ },
|
|
1090
|
+
{ keywords: ["network", "fetch"], type: "NETWORK_ERROR" /* NETWORK_ERROR */ },
|
|
1091
|
+
{ keywords: ["abort"], type: "ABORT_ERROR" /* ABORT_ERROR */ }
|
|
1092
|
+
];
|
|
1093
|
+
function resolveUserFriendlyErrorMessage(error) {
|
|
1094
|
+
if (error instanceof GroqError) {
|
|
1095
|
+
return USER_FRIENDLY_ERROR_MESSAGES[error.type];
|
|
1096
|
+
}
|
|
1097
|
+
if (error instanceof Error) {
|
|
1098
|
+
const lower = error.message.toLowerCase();
|
|
1099
|
+
for (const rule of KEYWORD_FALLBACK_RULES) {
|
|
1100
|
+
if (rule.keywords.some((kw) => lower.includes(kw))) {
|
|
1101
|
+
return USER_FRIENDLY_ERROR_MESSAGES[rule.type];
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
if (error.message.length > 0) return error.message;
|
|
1105
|
+
}
|
|
1106
|
+
return USER_FRIENDLY_ERROR_MESSAGES["UNKNOWN_ERROR" /* UNKNOWN_ERROR */];
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// src/domains/groq/utils/error-resolver.ts
|
|
1110
|
+
var resolveUserFriendlyMessage = resolveUserFriendlyErrorMessage;
|
|
1111
|
+
function formatErrorForLogging(error) {
|
|
1112
|
+
if (error instanceof GroqError) {
|
|
1113
|
+
return {
|
|
1114
|
+
message: error.message,
|
|
1115
|
+
type: error.type,
|
|
1116
|
+
stack: error.stack,
|
|
1117
|
+
metadata: error.metadata
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
if (error instanceof Error) {
|
|
1121
|
+
return { message: error.message, type: error.name, stack: error.stack };
|
|
1122
|
+
}
|
|
1123
|
+
return { message: String(error), type: typeof error };
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// src/domains/groq/hooks/use-groq.hook.ts
|
|
1127
|
+
function useGroq(options = {}, deps) {
|
|
1128
|
+
const textGeneration = deps?.textGeneration;
|
|
1129
|
+
if (!textGeneration) {
|
|
1130
|
+
throw new Error(
|
|
1131
|
+
"useGroq requires a TextGenerationService dependency. Provide it via the second argument or wire it through the app container."
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
1135
|
+
const [error, setError] = react.useState(null);
|
|
1136
|
+
const [result, setResult] = react.useState(null);
|
|
1137
|
+
const optionsRef = react.useRef(options);
|
|
1138
|
+
optionsRef.current = options;
|
|
1139
|
+
const abortControllerRef = react.useRef(null);
|
|
1140
|
+
const stableConfig = react.useMemo(
|
|
1141
|
+
() => ({
|
|
1142
|
+
model: options.model,
|
|
1143
|
+
generationConfig: options.generationConfig
|
|
1144
|
+
}),
|
|
1145
|
+
[
|
|
1146
|
+
options.model,
|
|
1147
|
+
options.generationConfig?.temperature,
|
|
1148
|
+
options.generationConfig?.maxTokens,
|
|
1149
|
+
options.generationConfig?.topP
|
|
1150
|
+
]
|
|
1151
|
+
);
|
|
1152
|
+
react.useEffect(() => {
|
|
1153
|
+
const controller = new AbortController();
|
|
1154
|
+
abortControllerRef.current = controller;
|
|
1155
|
+
return () => {
|
|
1156
|
+
controller.abort();
|
|
1157
|
+
abortControllerRef.current = null;
|
|
1158
|
+
};
|
|
1159
|
+
}, []);
|
|
1160
|
+
const runWithState = react.useCallback(
|
|
1161
|
+
async (task) => {
|
|
1162
|
+
const controller = abortControllerRef.current;
|
|
1163
|
+
if (!controller) {
|
|
1164
|
+
return null;
|
|
1165
|
+
}
|
|
1166
|
+
const { signal } = controller;
|
|
1167
|
+
if (signal.aborted) return null;
|
|
1168
|
+
setIsLoading(true);
|
|
1169
|
+
setError(null);
|
|
1170
|
+
setResult(null);
|
|
1171
|
+
const currentOptions = optionsRef.current;
|
|
1172
|
+
currentOptions.onStart?.();
|
|
1173
|
+
try {
|
|
1174
|
+
const value = await task(signal);
|
|
1175
|
+
return value;
|
|
1176
|
+
} catch (err) {
|
|
1177
|
+
if (signal.aborted) {
|
|
1178
|
+
return null;
|
|
1179
|
+
}
|
|
1180
|
+
const message = resolveUserFriendlyMessage(err);
|
|
1181
|
+
setError(message);
|
|
1182
|
+
currentOptions.onError?.(message);
|
|
1183
|
+
throw err;
|
|
1184
|
+
} finally {
|
|
1185
|
+
if (!signal.aborted) {
|
|
1186
|
+
setIsLoading(false);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
},
|
|
1190
|
+
[]
|
|
1191
|
+
);
|
|
1192
|
+
const generate = react.useCallback(
|
|
1193
|
+
(prompt, config) => runWithState(async () => {
|
|
1194
|
+
const response = await textGeneration.generateCompletion(prompt, {
|
|
1195
|
+
model: stableConfig.model,
|
|
1196
|
+
generationConfig: {
|
|
1197
|
+
...stableConfig.generationConfig,
|
|
1198
|
+
...config
|
|
1199
|
+
}
|
|
1200
|
+
});
|
|
1201
|
+
setResult(response);
|
|
1202
|
+
optionsRef.current.onSuccess?.(response);
|
|
1203
|
+
return response;
|
|
1204
|
+
}),
|
|
1205
|
+
[runWithState, textGeneration, stableConfig]
|
|
1206
|
+
);
|
|
1207
|
+
const generateJSON = react.useCallback(
|
|
1208
|
+
(prompt, config) => runWithState(async () => {
|
|
1209
|
+
const response = await textGeneration.generateStructured(prompt, {
|
|
1210
|
+
model: stableConfig.model,
|
|
1211
|
+
generationConfig: {
|
|
1212
|
+
...stableConfig.generationConfig,
|
|
1213
|
+
...config
|
|
1214
|
+
},
|
|
1215
|
+
...config?.schema !== void 0 ? { schema: config.schema } : {}
|
|
1216
|
+
});
|
|
1217
|
+
const json = JSON.stringify(response, null, 2);
|
|
1218
|
+
setResult(json);
|
|
1219
|
+
optionsRef.current.onSuccess?.(json);
|
|
1220
|
+
return response;
|
|
1221
|
+
}),
|
|
1222
|
+
[runWithState, textGeneration, stableConfig]
|
|
1223
|
+
);
|
|
1224
|
+
const stream = react.useCallback(
|
|
1225
|
+
(prompt, onChunk, config) => runWithState(async () => {
|
|
1226
|
+
const callbacks = {
|
|
1227
|
+
onChunk,
|
|
1228
|
+
onComplete: (text) => {
|
|
1229
|
+
setResult(text);
|
|
1230
|
+
optionsRef.current.onSuccess?.(text);
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
return textGeneration.streamCompletion(
|
|
1234
|
+
prompt,
|
|
1235
|
+
callbacks,
|
|
1236
|
+
{
|
|
1237
|
+
model: stableConfig.model,
|
|
1238
|
+
generationConfig: {
|
|
1239
|
+
...stableConfig.generationConfig,
|
|
1240
|
+
...config
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
);
|
|
1244
|
+
}),
|
|
1245
|
+
[runWithState, textGeneration, stableConfig]
|
|
1246
|
+
);
|
|
1247
|
+
const cancel = react.useCallback(() => {
|
|
1248
|
+
abortControllerRef.current?.abort();
|
|
1249
|
+
setIsLoading(false);
|
|
1250
|
+
}, []);
|
|
1251
|
+
const reset = react.useCallback(() => {
|
|
1252
|
+
cancel();
|
|
1253
|
+
setError(null);
|
|
1254
|
+
setResult(null);
|
|
1255
|
+
}, [cancel]);
|
|
1256
|
+
const clearError = react.useCallback(() => setError(null), []);
|
|
1257
|
+
return {
|
|
1258
|
+
isLoading,
|
|
1259
|
+
error,
|
|
1260
|
+
result,
|
|
1261
|
+
generate,
|
|
1262
|
+
generateJSON,
|
|
1263
|
+
stream,
|
|
1264
|
+
cancel,
|
|
1265
|
+
reset,
|
|
1266
|
+
clearError
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
1270
|
+
function useCacheStats(cache, options = {}) {
|
|
1271
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
1272
|
+
const enabled = options.enabled ?? true;
|
|
1273
|
+
const [snapshot, setSnapshot] = react.useState(() => cache.getStats());
|
|
1274
|
+
react.useEffect(() => {
|
|
1275
|
+
if (!enabled) return;
|
|
1276
|
+
setSnapshot(cache.getStats());
|
|
1277
|
+
const intervalId = setInterval(() => {
|
|
1278
|
+
setSnapshot(cache.getStats());
|
|
1279
|
+
}, pollIntervalMs);
|
|
1280
|
+
return () => clearInterval(intervalId);
|
|
1281
|
+
}, [cache, pollIntervalMs, enabled]);
|
|
1282
|
+
return snapshot;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// src/domains/groq/calculations/timing-window.ts
|
|
1286
|
+
function shouldCallOnLeadingEdge(params) {
|
|
1287
|
+
const { now, lastCallTime, interval, leadingEnabled, isThrottled } = params;
|
|
1288
|
+
if (!leadingEnabled) return false;
|
|
1289
|
+
if (isThrottled) return false;
|
|
1290
|
+
return now - lastCallTime >= interval;
|
|
1291
|
+
}
|
|
1292
|
+
function calculateTrailingDelay(params) {
|
|
1293
|
+
return Math.max(0, params.interval - params.timeSinceLastCall);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// src/domains/groq/utils/debounce.ts
|
|
1297
|
+
var DEFAULT_DEBOUNCE_DELAY_MS = 300;
|
|
1298
|
+
var DEFAULT_THROTTLE_INTERVAL_MS = 1e3;
|
|
1299
|
+
var DebounceManager = class {
|
|
1300
|
+
timers = /* @__PURE__ */ new Map();
|
|
1301
|
+
lastArgs = /* @__PURE__ */ new Map();
|
|
1302
|
+
debounce(fn, options = {}) {
|
|
1303
|
+
const delay = options.delay ?? DEFAULT_DEBOUNCE_DELAY_MS;
|
|
1304
|
+
const leading = options.leading ?? false;
|
|
1305
|
+
const trailing = options.trailing ?? true;
|
|
1306
|
+
let timer = null;
|
|
1307
|
+
let lastCallTime = 0;
|
|
1308
|
+
let lastResult;
|
|
1309
|
+
return ((...args) => {
|
|
1310
|
+
const now = Date.now();
|
|
1311
|
+
const shouldCallLeading = shouldCallOnLeadingEdge({
|
|
1312
|
+
now,
|
|
1313
|
+
lastCallTime,
|
|
1314
|
+
interval: delay,
|
|
1315
|
+
leadingEnabled: leading,
|
|
1316
|
+
isThrottled: timer !== null
|
|
1317
|
+
});
|
|
1318
|
+
if (timer) {
|
|
1319
|
+
clearTimeout(timer);
|
|
1320
|
+
timer = null;
|
|
1321
|
+
}
|
|
1322
|
+
if (shouldCallLeading) {
|
|
1323
|
+
lastCallTime = now;
|
|
1324
|
+
lastResult = fn(...args);
|
|
1325
|
+
return lastResult;
|
|
1326
|
+
}
|
|
1327
|
+
if (trailing) {
|
|
1328
|
+
timer = setTimeout(() => {
|
|
1329
|
+
timer = null;
|
|
1330
|
+
lastCallTime = Date.now();
|
|
1331
|
+
lastResult = fn(...args);
|
|
1332
|
+
}, delay);
|
|
1333
|
+
}
|
|
1334
|
+
return lastResult;
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
debounceByKey(key, fn, delay = DEFAULT_DEBOUNCE_DELAY_MS) {
|
|
1338
|
+
return ((...args) => {
|
|
1339
|
+
this.lastArgs.set(key, args);
|
|
1340
|
+
const existingTimer = this.timers.get(key);
|
|
1341
|
+
if (existingTimer) clearTimeout(existingTimer);
|
|
1342
|
+
const timer = setTimeout(() => {
|
|
1343
|
+
this.timers.delete(key);
|
|
1344
|
+
const pendingArgs = this.lastArgs.get(key);
|
|
1345
|
+
this.lastArgs.delete(key);
|
|
1346
|
+
if (pendingArgs) fn(...pendingArgs);
|
|
1347
|
+
}, delay);
|
|
1348
|
+
this.timers.set(key, timer);
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
cancelDebounce(key) {
|
|
1352
|
+
const timer = this.timers.get(key);
|
|
1353
|
+
if (timer) {
|
|
1354
|
+
clearTimeout(timer);
|
|
1355
|
+
this.timers.delete(key);
|
|
1356
|
+
}
|
|
1357
|
+
this.lastArgs.delete(key);
|
|
1358
|
+
}
|
|
1359
|
+
flushDebounce(key, fn) {
|
|
1360
|
+
this.cancelDebounce(key);
|
|
1361
|
+
const args = this.lastArgs.get(key);
|
|
1362
|
+
if (args) {
|
|
1363
|
+
fn(...args);
|
|
1364
|
+
this.lastArgs.delete(key);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
var ThrottleManager = class {
|
|
1369
|
+
timers = /* @__PURE__ */ new Map();
|
|
1370
|
+
lastArgs = /* @__PURE__ */ new Map();
|
|
1371
|
+
throttle(fn, options = {}) {
|
|
1372
|
+
const interval = options.interval ?? DEFAULT_THROTTLE_INTERVAL_MS;
|
|
1373
|
+
const leading = options.leading ?? true;
|
|
1374
|
+
const trailing = options.trailing ?? false;
|
|
1375
|
+
let timer = null;
|
|
1376
|
+
let lastCallTime = 0;
|
|
1377
|
+
let lastResult;
|
|
1378
|
+
let pendingArgs = null;
|
|
1379
|
+
return ((...args) => {
|
|
1380
|
+
const now = Date.now();
|
|
1381
|
+
const timeSinceLastCall = now - lastCallTime;
|
|
1382
|
+
const shouldCall = shouldCallOnLeadingEdge({
|
|
1383
|
+
now,
|
|
1384
|
+
lastCallTime,
|
|
1385
|
+
interval,
|
|
1386
|
+
leadingEnabled: leading,
|
|
1387
|
+
isThrottled: timer !== null
|
|
1388
|
+
});
|
|
1389
|
+
pendingArgs = args;
|
|
1390
|
+
if (shouldCall) {
|
|
1391
|
+
lastCallTime = now;
|
|
1392
|
+
lastResult = fn(...args);
|
|
1393
|
+
return lastResult;
|
|
1394
|
+
}
|
|
1395
|
+
if (trailing && !timer) {
|
|
1396
|
+
const delay = calculateTrailingDelay({ interval, timeSinceLastCall });
|
|
1397
|
+
timer = setTimeout(() => {
|
|
1398
|
+
timer = null;
|
|
1399
|
+
lastCallTime = Date.now();
|
|
1400
|
+
if (pendingArgs) {
|
|
1401
|
+
lastResult = fn(...pendingArgs);
|
|
1402
|
+
pendingArgs = null;
|
|
1403
|
+
}
|
|
1404
|
+
}, delay);
|
|
1405
|
+
}
|
|
1406
|
+
return lastResult;
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
throttleByKey(key, fn, interval = DEFAULT_THROTTLE_INTERVAL_MS) {
|
|
1410
|
+
return ((...args) => {
|
|
1411
|
+
const existingTimer = this.timers.get(key);
|
|
1412
|
+
if (existingTimer) {
|
|
1413
|
+
this.lastArgs.set(key, args);
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
fn(...args);
|
|
1417
|
+
const timer = setTimeout(() => {
|
|
1418
|
+
this.timers.delete(key);
|
|
1419
|
+
const pendingArgs = this.lastArgs.get(key);
|
|
1420
|
+
if (pendingArgs) {
|
|
1421
|
+
this.lastArgs.delete(key);
|
|
1422
|
+
fn(...pendingArgs);
|
|
1423
|
+
const nextTimer = setTimeout(() => {
|
|
1424
|
+
this.timers.delete(key);
|
|
1425
|
+
}, interval);
|
|
1426
|
+
this.timers.set(key, nextTimer);
|
|
1427
|
+
}
|
|
1428
|
+
}, interval);
|
|
1429
|
+
this.timers.set(key, timer);
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
cancelThrottle(key) {
|
|
1433
|
+
const timer = this.timers.get(key);
|
|
1434
|
+
if (timer) {
|
|
1435
|
+
clearTimeout(timer);
|
|
1436
|
+
this.timers.delete(key);
|
|
1437
|
+
}
|
|
1438
|
+
this.lastArgs.delete(key);
|
|
1439
|
+
}
|
|
1440
|
+
isThrottled(key) {
|
|
1441
|
+
return this.timers.has(key);
|
|
1442
|
+
}
|
|
1443
|
+
};
|
|
1444
|
+
var debounceManager = new DebounceManager();
|
|
1445
|
+
var throttleManager = new ThrottleManager();
|
|
1446
|
+
function debounce(fn, delay) {
|
|
1447
|
+
return debounceManager.debounce(fn, { delay });
|
|
1448
|
+
}
|
|
1449
|
+
function throttle(fn, interval) {
|
|
1450
|
+
return throttleManager.throttle(fn, { interval });
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
exports.API_ENDPOINTS = API_ENDPOINTS;
|
|
1454
|
+
exports.CacheManager = CacheManager;
|
|
1455
|
+
exports.CompositeExecutor = CompositeExecutor;
|
|
1456
|
+
exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
|
|
1457
|
+
exports.DEFAULT_GENERATION_CONFIG = DEFAULT_GENERATION_CONFIG;
|
|
1458
|
+
exports.DEFAULT_MODELS = DEFAULT_MODELS;
|
|
1459
|
+
exports.DedupeExecutor = DedupeExecutor;
|
|
1460
|
+
exports.GROQ_MODELS = GROQ_MODELS;
|
|
1461
|
+
exports.GroqChatRequestBuilder = GroqChatRequestBuilder;
|
|
1462
|
+
exports.GroqConfigStore = GroqConfigStore;
|
|
1463
|
+
exports.GroqError = GroqError;
|
|
1464
|
+
exports.GroqErrorType = GroqErrorType;
|
|
1465
|
+
exports.GroqHttpClient = GroqHttpClient;
|
|
1466
|
+
exports.GroqHttpTransport = GroqHttpTransport;
|
|
1467
|
+
exports.JsonResponseParser = JsonResponseParser;
|
|
1468
|
+
exports.QueueExecutor = QueueExecutor;
|
|
1469
|
+
exports.RequestDeduplicator = RequestDeduplicator;
|
|
1470
|
+
exports.RequestQueue = RequestQueue;
|
|
1471
|
+
exports.RetryExecutor = RetryExecutor;
|
|
1472
|
+
exports.RetryManager = RetryManager;
|
|
1473
|
+
exports.StreamingResponseParser = StreamingResponseParser;
|
|
1474
|
+
exports.StructuredPromptBuilder = StructuredPromptBuilder;
|
|
1475
|
+
exports.TIMEOUTS = TIMEOUTS;
|
|
1476
|
+
exports.TextGenerationService = TextGenerationService;
|
|
1477
|
+
exports.USER_FRIENDLY_ERROR_MESSAGES = USER_FRIENDLY_ERROR_MESSAGES;
|
|
1478
|
+
exports.cacheManager = cacheManager;
|
|
1479
|
+
exports.createAssistantMessage = createAssistantMessage;
|
|
1480
|
+
exports.createMessage = createMessage;
|
|
1481
|
+
exports.createSystemMessage = createSystemMessage;
|
|
1482
|
+
exports.createUserMessage = createUserMessage;
|
|
1483
|
+
exports.debounce = debounce;
|
|
1484
|
+
exports.debounceManager = debounceManager;
|
|
1485
|
+
exports.extractTextFromMessages = extractTextFromMessages;
|
|
1486
|
+
exports.formatErrorForLogging = formatErrorForLogging;
|
|
1487
|
+
exports.formatMessagesForDisplay = formatMessagesForDisplay;
|
|
1488
|
+
exports.promptToMessages = promptToMessages;
|
|
1489
|
+
exports.requestDeduplicator = requestDeduplicator;
|
|
1490
|
+
exports.requestQueue = requestQueue;
|
|
1491
|
+
exports.resolveUserFriendlyMessage = resolveUserFriendlyMessage;
|
|
1492
|
+
exports.retryManager = retryManager;
|
|
1493
|
+
exports.throttle = throttle;
|
|
1494
|
+
exports.throttleManager = throttleManager;
|
|
1495
|
+
exports.useCacheStats = useCacheStats;
|
|
1496
|
+
exports.useGroq = useGroq;
|
|
1497
|
+
exports.withRetry = withRetry;
|
|
1498
|
+
//# sourceMappingURL=index.cjs.map
|
|
1499
|
+
//# sourceMappingURL=index.cjs.map
|