@umituz/web-ai 1.0.0 → 1.0.2

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.
Files changed (55) hide show
  1. package/dist/{ABTesting-DDqNhHtP.d.ts → AIErrors-B2ogd1PB.d.ts} +14 -1
  2. package/dist/{ABTesting-DWBtKTMQ.d.cts → AIErrors-C5EasV_c.d.cts} +14 -1
  3. package/dist/{ProviderConfig-a55tLhWT.d.cts → ProviderConfig-DIFa9nmG.d.cts} +10 -1
  4. package/dist/{ProviderConfig-a55tLhWT.d.ts → ProviderConfig-DIFa9nmG.d.ts} +10 -1
  5. package/dist/chat/index.cjs +114 -23
  6. package/dist/chat/index.cjs.map +1 -1
  7. package/dist/chat/index.d.cts +4 -4
  8. package/dist/chat/index.d.ts +4 -4
  9. package/dist/chat/index.js +114 -23
  10. package/dist/chat/index.js.map +1 -1
  11. package/dist/{chat.service-DBKq9I2c.d.cts → chat.service-CL1qtYxe.d.cts} +7 -7
  12. package/dist/{chat.service-RQn8UTXD.d.ts → chat.service-q05GnhEm.d.ts} +7 -7
  13. package/dist/content/index.cjs +506 -193
  14. package/dist/content/index.cjs.map +1 -1
  15. package/dist/content/index.d.cts +7 -8
  16. package/dist/content/index.d.ts +7 -8
  17. package/dist/content/index.js +506 -193
  18. package/dist/content/index.js.map +1 -1
  19. package/dist/core/index.cjs +122 -15
  20. package/dist/core/index.cjs.map +1 -1
  21. package/dist/core/index.d.cts +13 -31
  22. package/dist/core/index.d.ts +13 -31
  23. package/dist/core/index.js +119 -16
  24. package/dist/core/index.js.map +1 -1
  25. package/dist/{create-ai-BhNaz86i.d.ts → create-ai-9PW752z8.d.ts} +14 -21
  26. package/dist/{create-ai-DGZFBOA1.d.cts → create-ai-CXYKPK6G.d.cts} +14 -21
  27. package/dist/hooks/index.cjs +631 -179
  28. package/dist/hooks/index.cjs.map +1 -1
  29. package/dist/hooks/index.d.cts +33 -7
  30. package/dist/hooks/index.d.ts +33 -7
  31. package/dist/hooks/index.js +631 -180
  32. package/dist/hooks/index.js.map +1 -1
  33. package/dist/index-DIHz8_OJ.d.ts +48 -0
  34. package/dist/index-DKHNcMbl.d.cts +48 -0
  35. package/dist/index.cjs +666 -104
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +8 -7
  38. package/dist/index.d.ts +8 -7
  39. package/dist/index.js +645 -105
  40. package/dist/index.js.map +1 -1
  41. package/dist/{provider.factory-COsnprLH.d.cts → provider.factory-BxsfK4WW.d.cts} +1 -1
  42. package/dist/{provider.factory-CpWFyjbw.d.ts → provider.factory-DfDfk0dK.d.ts} +1 -1
  43. package/dist/providers/index.cjs +973 -130
  44. package/dist/providers/index.cjs.map +1 -1
  45. package/dist/providers/index.d.cts +84 -18
  46. package/dist/providers/index.d.ts +84 -18
  47. package/dist/providers/index.js +956 -131
  48. package/dist/providers/index.js.map +1 -1
  49. package/dist/text-generation.service-BQO3Tvz2.d.cts +66 -0
  50. package/dist/text-generation.service-CcYQM6Ch.d.ts +66 -0
  51. package/dist/wizard/index.d.cts +1 -1
  52. package/dist/wizard/index.d.ts +1 -1
  53. package/package.json +9 -1
  54. package/dist/index-DjX9ZKuM.d.cts +0 -14
  55. package/dist/index-DxLvE588.d.ts +0 -14
@@ -3,6 +3,17 @@ import { useMemo, useCallback, useState, useRef, useEffect } from 'react';
3
3
  // src/hooks/useAIContent.ts
4
4
 
5
5
  // src/core/errors/ai-error-types.ts
6
+ var AIErrorType = /* @__PURE__ */ ((AIErrorType2) => {
7
+ AIErrorType2["INVALID_API_KEY"] = "INVALID_API_KEY";
8
+ AIErrorType2["MISSING_CONFIG"] = "MISSING_CONFIG";
9
+ AIErrorType2["NETWORK_ERROR"] = "NETWORK_ERROR";
10
+ AIErrorType2["ABORT_ERROR"] = "ABORT_ERROR";
11
+ AIErrorType2["RATE_LIMIT_ERROR"] = "RATE_LIMIT_ERROR";
12
+ AIErrorType2["SERVER_ERROR"] = "SERVER_ERROR";
13
+ AIErrorType2["CLIENT_ERROR"] = "CLIENT_ERROR";
14
+ AIErrorType2["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
15
+ return AIErrorType2;
16
+ })(AIErrorType || {});
6
17
  var USER_FRIENDLY_ERROR_MESSAGES = {
7
18
  ["INVALID_API_KEY" /* INVALID_API_KEY */]: "Please check your API key and try again.",
8
19
  ["MISSING_CONFIG" /* MISSING_CONFIG */]: "The provider is not configured. Please create a provider with createAI first.",
@@ -15,17 +26,80 @@ var USER_FRIENDLY_ERROR_MESSAGES = {
15
26
  };
16
27
 
17
28
  // src/core/errors/ai-error.ts
18
- var AIError = class extends Error {
29
+ var KNOWN_ERROR_TYPES = new Set(Object.values(AIErrorType));
30
+ function mapCodeToType(code) {
31
+ switch (code) {
32
+ case "VALIDATION_ERROR":
33
+ case "CLIENT_ERROR":
34
+ return "CLIENT_ERROR" /* CLIENT_ERROR */;
35
+ case "INVALID_API_KEY":
36
+ return "INVALID_API_KEY" /* INVALID_API_KEY */;
37
+ case "MISSING_CONFIG":
38
+ case "PROVIDER_UNAVAILABLE":
39
+ return "MISSING_CONFIG" /* MISSING_CONFIG */;
40
+ case "PROVIDER_QUOTA_EXHAUSTED":
41
+ case "RATE_LIMIT_ERROR":
42
+ return "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */;
43
+ case "NETWORK_ERROR":
44
+ case "TIMEOUT":
45
+ return "NETWORK_ERROR" /* NETWORK_ERROR */;
46
+ case "ABORT_ERROR":
47
+ case "ABORTED":
48
+ return "ABORT_ERROR" /* ABORT_ERROR */;
49
+ case "SERVER_ERROR":
50
+ case "PROVIDER_ERROR":
51
+ case "NO_RESULT":
52
+ return "SERVER_ERROR" /* SERVER_ERROR */;
53
+ default:
54
+ return "UNKNOWN_ERROR" /* UNKNOWN_ERROR */;
55
+ }
56
+ }
57
+ function mapMessageToCode(message, fallback = "UNKNOWN") {
58
+ const lower = message.toLowerCase();
59
+ if (lower.includes("validation")) return "VALIDATION_ERROR";
60
+ if (lower.includes("quota")) return "PROVIDER_QUOTA_EXHAUSTED";
61
+ if (lower.includes("unavailable")) return "PROVIDER_UNAVAILABLE";
62
+ if (lower.includes("timeout") || lower.includes("aborted")) {
63
+ return lower.includes("aborted") ? "ABORTED" : "TIMEOUT";
64
+ }
65
+ if (lower.includes("parse") || lower.includes("json")) return "PARSE_ERROR";
66
+ if (lower.includes("no result")) return "NO_RESULT";
67
+ if (lower.includes("network") || lower.includes("fetch")) return "NETWORK_ERROR";
68
+ return fallback;
69
+ }
70
+ var AIError = class _AIError extends Error {
19
71
  type;
72
+ code;
20
73
  cause;
21
74
  metadata;
22
- constructor(type, message, cause, metadata = {}) {
23
- super(message);
75
+ constructor(typeOrMessage, messageOrCode, cause, metadata = {}) {
76
+ let effectiveType;
77
+ let effectiveMessage;
78
+ let effectiveCode;
79
+ if (KNOWN_ERROR_TYPES.has(typeOrMessage)) {
80
+ effectiveType = typeOrMessage;
81
+ effectiveMessage = messageOrCode ?? "";
82
+ effectiveCode = effectiveType;
83
+ } else {
84
+ effectiveMessage = typeOrMessage;
85
+ effectiveCode = messageOrCode ?? "UNKNOWN";
86
+ effectiveType = mapCodeToType(effectiveCode);
87
+ }
88
+ super(effectiveMessage);
24
89
  this.name = "AIError";
25
- this.type = type;
90
+ this.type = effectiveType;
91
+ this.code = effectiveCode;
26
92
  this.cause = cause;
27
93
  this.metadata = metadata;
28
94
  }
95
+ static from(error, fallbackCode = "UNKNOWN") {
96
+ if (error instanceof _AIError) return error;
97
+ if (error instanceof Error) {
98
+ const code = mapMessageToCode(error.message, fallbackCode);
99
+ return new _AIError(error.message, code, error);
100
+ }
101
+ return new _AIError(String(error), fallbackCode, error);
102
+ }
29
103
  toString() {
30
104
  const statusPart = typeof this.metadata.status === "number" ? ` (HTTP ${this.metadata.status})` : "";
31
105
  return `${this.name} [${this.type}]: ${this.message}${statusPart}`;
@@ -34,12 +108,21 @@ var AIError = class extends Error {
34
108
  return {
35
109
  name: this.name,
36
110
  type: this.type,
111
+ code: this.code,
37
112
  message: this.message,
38
113
  metadata: this.metadata
39
114
  };
40
115
  }
41
116
  };
42
117
 
118
+ // src/core/errors/groq-error.ts
119
+ var GroqError = class extends AIError {
120
+ constructor(type, message, cause, metadata = {}) {
121
+ super(type, message, cause, metadata);
122
+ this.name = "GroqError";
123
+ }
124
+ };
125
+
43
126
  // src/core/errors/error-classifier.ts
44
127
  var DEFAULT_RETRYABLE_TYPES = /* @__PURE__ */ new Set([
45
128
  "NETWORK_ERROR" /* NETWORK_ERROR */,
@@ -222,12 +305,16 @@ var ProviderFactory = class {
222
305
  let wake = null;
223
306
  let finished = false;
224
307
  let failure;
308
+ const controller = new AbortController();
309
+ const onAbort = () => controller.abort();
310
+ request.signal?.addEventListener("abort", onAbort);
311
+ const effectiveRequest = { ...request, signal: controller.signal };
225
312
  const notify = () => {
226
313
  const resolve = wake;
227
314
  wake = null;
228
315
  resolve?.();
229
316
  };
230
- const completion = this.chatStream(request, {
317
+ const completion = this.chatStream(effectiveRequest, {
231
318
  onChunk: (chunk) => {
232
319
  queue.push(chunk);
233
320
  notify();
@@ -243,18 +330,23 @@ var ProviderFactory = class {
243
330
  notify();
244
331
  }
245
332
  );
246
- while (!finished || queue.length > 0) {
247
- if (queue.length === 0 && !finished) {
248
- await new Promise((resolve) => {
249
- wake = resolve;
250
- });
251
- }
252
- while (queue.length > 0) {
253
- yield queue.shift();
333
+ try {
334
+ while (!finished || queue.length > 0) {
335
+ if (queue.length === 0 && !finished) {
336
+ await new Promise((resolve) => {
337
+ wake = resolve;
338
+ });
339
+ }
340
+ while (queue.length > 0) {
341
+ yield queue.shift();
342
+ }
343
+ if (failure) throw failure;
254
344
  }
255
- if (failure) throw failure;
345
+ await completion;
346
+ } finally {
347
+ controller.abort();
348
+ request.signal?.removeEventListener("abort", onAbort);
256
349
  }
257
- await completion;
258
350
  }
259
351
  async generateJson(request) {
260
352
  const effective = request.model || request.tier ? request : { ...request, tier: this.defaultTier };
@@ -401,15 +493,22 @@ var ProviderFactory = class {
401
493
  // src/core/constants/protocol.constants.ts
402
494
  var SSE_PREFIX = "data: ";
403
495
  var SSE_DONE_MARKER = "[DONE]";
404
- var JSON_CODE_BLOCK_PATTERN = /```json\n([\s\S]*?)\n```/;
496
+ var JSON_CODE_BLOCK_PATTERN = /```(?:json)?\s*\n?([\s\S]*?)\n?```/;
405
497
  var JSON_OBJECT_PATTERN = /\{[\s\S]*\}/;
498
+ var JSON_ARRAY_PATTERN = /\[[\s\S]*\]/;
406
499
 
407
500
  // src/core/parsers/sse-lines.ts
408
501
  function isSseDataLine(line) {
409
- return line.startsWith(SSE_PREFIX);
502
+ return line.startsWith(SSE_PREFIX) || line.startsWith("data:");
410
503
  }
411
504
  function extractSseDataPayload(line) {
412
- return line.slice(SSE_PREFIX.length);
505
+ if (line.startsWith(SSE_PREFIX)) {
506
+ return line.slice(SSE_PREFIX.length);
507
+ }
508
+ if (line.startsWith("data:")) {
509
+ return line.slice(5);
510
+ }
511
+ return line;
413
512
  }
414
513
  function isSseDoneMarker(payload) {
415
514
  return payload === SSE_DONE_MARKER;
@@ -418,7 +517,7 @@ function isSseDoneMarker(payload) {
418
517
  // src/core/parsers/json-content-extractor.ts
419
518
  function extractJsonFromCodeFence(content) {
420
519
  const match = content.match(JSON_CODE_BLOCK_PATTERN);
421
- if (match && match[1]) return match[1];
520
+ if (match && match[1]) return match[1].trim();
422
521
  return null;
423
522
  }
424
523
  function extractJsonObject(content) {
@@ -426,8 +525,27 @@ function extractJsonObject(content) {
426
525
  if (match && match[0]) return match[0];
427
526
  return null;
428
527
  }
528
+ function extractJsonArray(content) {
529
+ const match = content.match(JSON_ARRAY_PATTERN);
530
+ if (match && match[0]) return match[0];
531
+ return null;
532
+ }
533
+ function repairCommonJsonDefects(text) {
534
+ return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
535
+ }
429
536
  function extractJsonCandidate(content) {
430
- return extractJsonFromCodeFence(content) ?? extractJsonObject(content) ?? content;
537
+ const fromFence = extractJsonFromCodeFence(content);
538
+ if (fromFence) return fromFence;
539
+ const objectIndex = content.indexOf("{");
540
+ const arrayIndex = content.indexOf("[");
541
+ if (objectIndex !== -1 && (arrayIndex === -1 || objectIndex < arrayIndex)) {
542
+ const obj = extractJsonObject(content);
543
+ if (obj) return obj;
544
+ } else if (arrayIndex !== -1) {
545
+ const arr = extractJsonArray(content);
546
+ if (arr) return arr;
547
+ }
548
+ return content.trim();
431
549
  }
432
550
 
433
551
  // src/core/parsers/json-response.parser.ts
@@ -436,12 +554,17 @@ var JsonResponseParser = class {
436
554
  const candidate = extractJsonCandidate(rawContent);
437
555
  try {
438
556
  return JSON.parse(candidate);
439
- } catch (error) {
440
- throw new AIError(
441
- "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
442
- "Failed to parse structured response as JSON",
443
- error instanceof Error ? error : void 0
444
- );
557
+ } catch {
558
+ try {
559
+ return JSON.parse(repairCommonJsonDefects(candidate));
560
+ } catch (error) {
561
+ throw new AIError(
562
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
563
+ "Failed to parse structured response as JSON",
564
+ error instanceof Error ? error : void 0,
565
+ { rawContent }
566
+ );
567
+ }
445
568
  }
446
569
  }
447
570
  };
@@ -755,11 +878,14 @@ async function consumeAnthropicStream(stream, callbacks) {
755
878
  break;
756
879
  case "message_stop":
757
880
  break;
758
- case "error":
881
+ case "error": {
882
+ const errType = event.error.type;
883
+ const mappedType = errType === "rate_limit_error" ? "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ : errType === "authentication_error" ? "INVALID_API_KEY" /* INVALID_API_KEY */ : errType === "invalid_request_error" ? "CLIENT_ERROR" /* CLIENT_ERROR */ : "SERVER_ERROR" /* SERVER_ERROR */;
759
884
  throw new AIError(
760
- event.error.type === "rate_limit_error" ? "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ : "SERVER_ERROR" /* SERVER_ERROR */,
885
+ mappedType,
761
886
  event.error.message || "Anthropic stream failed mid-flight"
762
887
  );
888
+ }
763
889
  }
764
890
  };
765
891
  try {
@@ -1221,6 +1347,27 @@ var RequestDeduplicator = class {
1221
1347
  }
1222
1348
  };
1223
1349
 
1350
+ // src/core/utils/api-key-validator.ts
1351
+ var DEFAULT_API_KEY_RULE = {
1352
+ minLength: 10
1353
+ };
1354
+ function validateApiKey(apiKey, rule = DEFAULT_API_KEY_RULE) {
1355
+ if (!apiKey) {
1356
+ return { valid: false, reason: "API key is required" };
1357
+ }
1358
+ const trimmed = apiKey.trim();
1359
+ if (trimmed.length === 0) {
1360
+ return { valid: false, reason: "API key cannot be empty" };
1361
+ }
1362
+ if (trimmed.length < rule.minLength) {
1363
+ return {
1364
+ valid: false,
1365
+ reason: `API key must be at least ${rule.minLength} characters`
1366
+ };
1367
+ }
1368
+ return { valid: true };
1369
+ }
1370
+
1224
1371
  // src/providers/groq/groq.constants.ts
1225
1372
  var GROQ_MODELS = {
1226
1373
  LLAMA_3_1_8B_INSTANT: "llama-3.1-8b-instant",
@@ -1237,11 +1384,13 @@ var DEFAULT_GENERATION_CONFIG = {
1237
1384
  topP: 1
1238
1385
  };
1239
1386
  var GROQ_BASE_URL = "https://api.groq.com/openai/v1";
1387
+ var DEFAULT_BASE_URL = GROQ_BASE_URL;
1240
1388
  var CHAT_COMPLETIONS_PATH = "/chat/completions";
1241
1389
  var GROQ_TIMEOUTS = {
1242
1390
  DEFAULT: 3e4,
1243
1391
  STREAMING: 6e4
1244
1392
  };
1393
+ var TIMEOUTS = GROQ_TIMEOUTS;
1245
1394
  var HTTP_HEADERS = {
1246
1395
  CONTENT_TYPE: "Content-Type",
1247
1396
  APPLICATION_JSON: "application/json",
@@ -1300,13 +1449,65 @@ var GroqChatRequestBuilder = class _GroqChatRequestBuilder {
1300
1449
  }
1301
1450
  };
1302
1451
 
1452
+ // src/providers/groq/groq-config-store.ts
1453
+ var GroqConfigStore = class {
1454
+ config = null;
1455
+ initialize(config) {
1456
+ const validation = validateApiKey(config.apiKey, { minLength: 1 });
1457
+ if (!validation.valid) {
1458
+ throw new GroqError("INVALID_API_KEY" /* INVALID_API_KEY */, validation.reason ?? "Invalid API key");
1459
+ }
1460
+ const normalized = {
1461
+ apiKey: config.apiKey.trim(),
1462
+ baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
1463
+ timeoutMs: config.timeoutMs ?? TIMEOUTS.DEFAULT,
1464
+ ...config.models !== void 0 ? { models: config.models } : {},
1465
+ ...config.textModel !== void 0 ? { textModel: config.textModel } : {}
1466
+ };
1467
+ this.config = normalized;
1468
+ return normalized;
1469
+ }
1470
+ get() {
1471
+ if (!this.config) {
1472
+ throw new GroqError(
1473
+ "MISSING_CONFIG" /* MISSING_CONFIG */,
1474
+ "Client not initialized. Call configureProvider first."
1475
+ );
1476
+ }
1477
+ return this.config;
1478
+ }
1479
+ getOrNull() {
1480
+ return this.config;
1481
+ }
1482
+ isInitialized() {
1483
+ return this.config !== null;
1484
+ }
1485
+ setApiKey(apiKey) {
1486
+ const current = this.get();
1487
+ const validation = validateApiKey(apiKey, { minLength: 1 });
1488
+ if (!validation.valid) {
1489
+ throw new GroqError("INVALID_API_KEY" /* INVALID_API_KEY */, validation.reason ?? "Invalid API key");
1490
+ }
1491
+ this.config = { ...current, apiKey: apiKey.trim() };
1492
+ }
1493
+ reset() {
1494
+ this.config = null;
1495
+ }
1496
+ };
1497
+
1303
1498
  // src/providers/groq/groq-http-transport.ts
1304
1499
  var GroqHttpTransport = class {
1305
- constructor(config) {
1306
- this.config = config;
1500
+ constructor(configSource) {
1501
+ this.configSource = configSource;
1307
1502
  }
1308
- config;
1503
+ configSource;
1309
1504
  pendingAbortControllers = /* @__PURE__ */ new Set();
1505
+ getConfig() {
1506
+ if ("get" in this.configSource && typeof this.configSource.get === "function") {
1507
+ return this.configSource.get();
1508
+ }
1509
+ return this.configSource;
1510
+ }
1310
1511
  cancelPendingRequests() {
1311
1512
  for (const controller of this.pendingAbortControllers) {
1312
1513
  controller.abort();
@@ -1321,7 +1522,7 @@ var GroqHttpTransport = class {
1321
1522
  }
1322
1523
  async streamChatCompletion(request, options = {}) {
1323
1524
  const url = `${this.baseUrl()}${CHAT_COMPLETIONS_PATH}`;
1324
- const timeout = this.config.timeoutMs ?? GROQ_TIMEOUTS.STREAMING;
1525
+ const timeout = this.getConfig().timeoutMs ?? GROQ_TIMEOUTS.STREAMING;
1325
1526
  const controller = new AbortController();
1326
1527
  const timeoutId = setTimeout(() => controller.abort(), timeout);
1327
1528
  const onExternalAbort = () => controller.abort();
@@ -1348,11 +1549,11 @@ var GroqHttpTransport = class {
1348
1549
  }
1349
1550
  }
1350
1551
  baseUrl() {
1351
- return this.config.baseUrl ?? GROQ_BASE_URL;
1552
+ return this.getConfig().baseUrl ?? GROQ_BASE_URL;
1352
1553
  }
1353
1554
  async request(endpoint, body) {
1354
1555
  const url = `${this.baseUrl()}${endpoint}`;
1355
- const timeout = this.config.timeoutMs ?? GROQ_TIMEOUTS.DEFAULT;
1556
+ const timeout = this.getConfig().timeoutMs ?? GROQ_TIMEOUTS.DEFAULT;
1356
1557
  const controller = new AbortController();
1357
1558
  const timeoutId = setTimeout(() => controller.abort(), timeout);
1358
1559
  this.pendingAbortControllers.add(controller);
@@ -1375,7 +1576,7 @@ var GroqHttpTransport = class {
1375
1576
  buildHeaders() {
1376
1577
  return {
1377
1578
  [HTTP_HEADERS.CONTENT_TYPE]: HTTP_HEADERS.APPLICATION_JSON,
1378
- [HTTP_HEADERS.AUTHORIZATION]: `${HTTP_HEADERS.BEARER_PREFIX}${this.config.apiKey}`
1579
+ [HTTP_HEADERS.AUTHORIZATION]: `${HTTP_HEADERS.BEARER_PREFIX}${this.getConfig().apiKey}`
1379
1580
  };
1380
1581
  }
1381
1582
  async throwFromErrorResponse(response) {
@@ -1477,21 +1678,40 @@ var CompositeExecutor = class {
1477
1678
 
1478
1679
  // src/providers/groq/groq-http-client.ts
1479
1680
  var GroqHttpClient = class {
1681
+ configStore;
1480
1682
  transport;
1481
1683
  executor;
1482
- constructor(config, deps = {}) {
1483
- this.transport = deps.transport ?? new GroqHttpTransport(config);
1684
+ constructor(configOrDeps, maybeDeps = {}) {
1685
+ const isConfig = typeof configOrDeps === "object" && configOrDeps !== null && "apiKey" in configOrDeps && typeof configOrDeps.apiKey === "string";
1686
+ const deps = isConfig ? maybeDeps : configOrDeps ?? {};
1687
+ this.configStore = deps.configStore ?? new GroqConfigStore();
1688
+ if (isConfig) {
1689
+ this.configStore.initialize(configOrDeps);
1690
+ }
1691
+ this.transport = deps.transport ?? new GroqHttpTransport(this.configStore);
1484
1692
  const retryManager2 = deps.retryManager ?? new RetryManager();
1485
1693
  const queue = deps.queue ?? new RequestQueue();
1486
1694
  const deduplicator = deps.deduplicator ?? new RequestDeduplicator();
1487
1695
  this.executor = new CompositeExecutor([
1488
1696
  new DedupeExecutor(deduplicator, {
1489
- generateKey: (request) => deduplicatorKey(request, config.apiKey)
1697
+ generateKey: (request) => deduplicatorKey(request, this.configStore.isInitialized() ? this.configStore.get().apiKey : "")
1490
1698
  }),
1491
1699
  new RetryExecutor(retryManager2),
1492
1700
  new QueueExecutor(queue, QUEUE_PRIORITY_USER_REQUEST)
1493
1701
  ]);
1494
1702
  }
1703
+ initialize(config) {
1704
+ this.configStore.initialize(config);
1705
+ }
1706
+ isInitialized() {
1707
+ return this.configStore.isInitialized();
1708
+ }
1709
+ getApiKey() {
1710
+ return this.configStore.get().apiKey;
1711
+ }
1712
+ setApiKey(apiKey) {
1713
+ this.configStore.setApiKey(apiKey);
1714
+ }
1495
1715
  cancelPendingRequests() {
1496
1716
  this.transport.cancelPendingRequests();
1497
1717
  }
@@ -1504,6 +1724,10 @@ var GroqHttpClient = class {
1504
1724
  streamChatCompletion(request, options) {
1505
1725
  return this.transport.streamChatCompletion(request, options);
1506
1726
  }
1727
+ reset() {
1728
+ this.transport.cancelPendingRequests();
1729
+ this.configStore.reset();
1730
+ }
1507
1731
  };
1508
1732
  function deduplicatorKey(request, apiKey) {
1509
1733
  const { model, messages, stream: _stream, ...rest } = request;
@@ -1551,6 +1775,13 @@ async function consumeGroqStream(stream, callbacks) {
1551
1775
  } catch {
1552
1776
  return;
1553
1777
  }
1778
+ const errorBody = chunk.error;
1779
+ if (errorBody) {
1780
+ throw new AIError(
1781
+ errorBody.message?.toLowerCase().includes("rate limit") ? "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ : "SERVER_ERROR" /* SERVER_ERROR */,
1782
+ errorBody.message || "Groq stream failed mid-flight"
1783
+ );
1784
+ }
1554
1785
  if (chunk.model) model = chunk.model;
1555
1786
  const choice = chunk.choices?.[0];
1556
1787
  const delta = choice?.delta?.content;
@@ -1565,13 +1796,6 @@ async function consumeGroqStream(stream, callbacks) {
1565
1796
  if (tailUsage) {
1566
1797
  usage = mapGroqUsage(tailUsage);
1567
1798
  }
1568
- const errorBody = chunk.error;
1569
- if (errorBody) {
1570
- throw new AIError(
1571
- "SERVER_ERROR" /* SERVER_ERROR */,
1572
- errorBody.message || "Groq stream failed mid-flight"
1573
- );
1574
- }
1575
1799
  };
1576
1800
  try {
1577
1801
  while (true) {
@@ -1772,37 +1996,7 @@ function findLastIndex2(items, predicate) {
1772
1996
  }
1773
1997
 
1774
1998
  // src/content/domain/errors/AIErrors.ts
1775
- var AIError2 = class _AIError extends Error {
1776
- code;
1777
- cause;
1778
- constructor(message, code, cause) {
1779
- super(message);
1780
- this.name = "AIError";
1781
- this.code = code;
1782
- this.cause = cause;
1783
- }
1784
- static from(error, fallbackCode = "UNKNOWN") {
1785
- if (error instanceof _AIError) return error;
1786
- if (error instanceof Error) {
1787
- return new _AIError(error.message, mapMessageToCode(error.message, fallbackCode), error);
1788
- }
1789
- return new _AIError(String(error), fallbackCode, error);
1790
- }
1791
- };
1792
- function mapMessageToCode(message, fallback) {
1793
- const lower = message.toLowerCase();
1794
- if (lower.includes("validation")) return "VALIDATION_ERROR";
1795
- if (lower.includes("quota")) return "PROVIDER_QUOTA_EXHAUSTED";
1796
- if (lower.includes("unavailable")) return "PROVIDER_UNAVAILABLE";
1797
- if (lower.includes("timeout") || lower.includes("aborted")) {
1798
- return lower.includes("aborted") ? "ABORTED" : "TIMEOUT";
1799
- }
1800
- if (lower.includes("parse") || lower.includes("json")) return "PARSE_ERROR";
1801
- if (lower.includes("no result")) return "NO_RESULT";
1802
- if (lower.includes("network") || lower.includes("fetch")) return "NETWORK_ERROR";
1803
- return fallback;
1804
- }
1805
- var ValidationError = class extends AIError2 {
1999
+ var ValidationError = class extends AIError {
1806
2000
  constructor(message) {
1807
2001
  super(message, "VALIDATION_ERROR");
1808
2002
  this.name = "ValidationError";
@@ -1933,7 +2127,9 @@ var GeminiProvider = class extends BaseProvider {
1933
2127
  headers: this.authHeaders()
1934
2128
  }
1935
2129
  );
1936
- return response.ok ? "healthy" : "unavailable";
2130
+ if (response.ok) return "healthy";
2131
+ if (response.status === 401 || response.status === 403) return "unavailable";
2132
+ return "degraded";
1937
2133
  } catch {
1938
2134
  return "unavailable";
1939
2135
  }
@@ -1944,6 +2140,8 @@ var GeminiProvider = class extends BaseProvider {
1944
2140
  async generate(request) {
1945
2141
  try {
1946
2142
  const model = request.model ?? this.resolveModel(request.tier ?? "balanced");
2143
+ const systemMessages = request.messages.filter((m) => m.role === "system");
2144
+ const systemPrompt = request.systemPrompt && request.systemPrompt.length > 0 ? request.systemPrompt : systemMessages.length > 0 ? systemMessages.map((m) => m.content).join("\n") : void 0;
1947
2145
  const response = await this.withRetry(async () => {
1948
2146
  return await this.fetchWithTimeout(
1949
2147
  `${this.config.baseUrl}/models/${model}:generateContent`,
@@ -1951,22 +2149,28 @@ var GeminiProvider = class extends BaseProvider {
1951
2149
  method: "POST",
1952
2150
  headers: this.authHeaders(),
1953
2151
  body: JSON.stringify({
1954
- systemInstruction: request.systemPrompt && request.systemPrompt.length > 0 ? { parts: [{ text: request.systemPrompt }] } : void 0,
2152
+ systemInstruction: systemPrompt && systemPrompt.length > 0 ? { parts: [{ text: systemPrompt }] } : void 0,
1955
2153
  contents: toGeminiContents(request.messages),
1956
2154
  generationConfig: {
1957
- temperature: request.temperature || 0.7,
1958
- maxOutputTokens: request.maxTokens || 2048,
1959
- topP: request.topP || 0.9
2155
+ temperature: request.temperature ?? 0.7,
2156
+ maxOutputTokens: request.maxTokens ?? 2048,
2157
+ topP: request.topP ?? 0.9
1960
2158
  }
1961
2159
  })
1962
2160
  }
1963
2161
  );
1964
2162
  });
1965
2163
  if (!response.ok) {
1966
- throw new Error(`Gemini API error: ${response.statusText}`);
2164
+ const message = await extractErrorMessageFromResponse(response);
2165
+ throw new AIError(
2166
+ classifyHttpStatus(response.status),
2167
+ `Gemini API error (${response.status}): ${message}`,
2168
+ void 0,
2169
+ { status: response.status, model }
2170
+ );
1967
2171
  }
1968
2172
  const data = await response.json();
1969
- const text = data.candidates[0]?.content?.parts?.map((part) => part.text ?? "").join("") ?? "";
2173
+ const text = data.candidates?.[0]?.content?.parts?.map((part) => part.text ?? "").join("") ?? "";
1970
2174
  if (text.length === 0) {
1971
2175
  throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No content generated from Gemini API", void 0, { model });
1972
2176
  }
@@ -1979,7 +2183,7 @@ var GeminiProvider = class extends BaseProvider {
1979
2183
  } : void 0,
1980
2184
  model,
1981
2185
  providerId: this.id,
1982
- finishReason: mapGeminiFinishReason(data.candidates[0]?.finishReason)
2186
+ finishReason: mapGeminiFinishReason(data.candidates?.[0]?.finishReason)
1983
2187
  };
1984
2188
  } catch (error) {
1985
2189
  if (error instanceof AIError) throw error;
@@ -2031,12 +2235,18 @@ var GeminiProvider = class extends BaseProvider {
2031
2235
  );
2032
2236
  });
2033
2237
  if (!response.ok) {
2034
- throw new Error(`Gemini API error: ${response.statusText}`);
2238
+ const message = await extractErrorMessageFromResponse(response);
2239
+ throw new AIError(
2240
+ classifyHttpStatus(response.status),
2241
+ `Gemini image generation error (${response.status}): ${message}`,
2242
+ void 0,
2243
+ { status: response.status }
2244
+ );
2035
2245
  }
2036
2246
  const data = await response.json();
2037
- const imageData = data.candidates[0]?.content?.parts[0]?.inlineData;
2247
+ const imageData = data.candidates?.[0]?.content?.parts?.[0]?.inlineData;
2038
2248
  if (!imageData) {
2039
- throw new Error("No image data in response");
2249
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No image data in Gemini response");
2040
2250
  }
2041
2251
  const url = `data:${imageData.mimeType};base64,${imageData.data}`;
2042
2252
  return {
@@ -2465,17 +2675,70 @@ var PrunaProvider = class {
2465
2675
  async estimateCost(_request) {
2466
2676
  return 1;
2467
2677
  }
2468
- async generateImage(_request) {
2469
- throw new AIError("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2678
+ async generateImage(request) {
2679
+ let finalUrl;
2680
+ const model = this.config.models?.textToImage;
2681
+ for await (const stage of generateWithPruna(
2682
+ { prompt: request.prompt, model, quality: request.quality },
2683
+ this.config.apiKey
2684
+ )) {
2685
+ if (stage.error) throw stage.error;
2686
+ if (stage.result?.url) finalUrl = stage.result.url;
2687
+ }
2688
+ if (!finalUrl) {
2689
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No image generated by Pruna AI");
2690
+ }
2691
+ return {
2692
+ id: Date.now().toString(),
2693
+ type: "image",
2694
+ url: finalUrl,
2695
+ metadata: { provider: this.id, model: model ?? "p-image" },
2696
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2697
+ };
2470
2698
  }
2471
- async generateVideo(_request) {
2472
- throw new AIError("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2699
+ async generateVideo(request) {
2700
+ let finalUrl;
2701
+ const model = this.config.models?.textToVideo;
2702
+ for await (const stage of generateWithPruna(
2703
+ { prompt: request.prompt, model, duration: request.duration, aspectRatio: request.aspectRatio },
2704
+ this.config.apiKey
2705
+ )) {
2706
+ if (stage.error) throw stage.error;
2707
+ if (stage.result?.url) finalUrl = stage.result.url;
2708
+ }
2709
+ if (!finalUrl) {
2710
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated by Pruna AI");
2711
+ }
2712
+ return {
2713
+ id: Date.now().toString(),
2714
+ type: "video",
2715
+ url: finalUrl,
2716
+ metadata: { provider: this.id, model: model ?? "p-video" },
2717
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2718
+ };
2473
2719
  }
2474
- async imageToVideo(_request) {
2475
- throw new AIError("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2720
+ async imageToVideo(request) {
2721
+ let finalUrl;
2722
+ for await (const stage of generateWithPruna(
2723
+ { image: request.imageUrl, prompt: request.prompt, duration: request.duration, motion: request.motion },
2724
+ this.config.apiKey
2725
+ )) {
2726
+ if (stage.error) throw stage.error;
2727
+ if (stage.result?.url) finalUrl = stage.result.url;
2728
+ }
2729
+ if (!finalUrl) {
2730
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated from image by Pruna AI");
2731
+ }
2732
+ return {
2733
+ id: Date.now().toString(),
2734
+ type: "video",
2735
+ url: finalUrl,
2736
+ metadata: { provider: this.id, model: "p-image-to-video" },
2737
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2738
+ };
2476
2739
  }
2477
2740
  async videoToVideo(_request) {
2478
- throw new AIError("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2741
+ throw new AIError("CLIENT_ERROR" /* CLIENT_ERROR */, "Pruna AI provider does not support video-to-video conversion");
2479
2742
  }
2480
2743
  };
2481
2744
 
@@ -3082,11 +3345,12 @@ var buildScriptResult = (parsed, duration) => {
3082
3345
  if (!script) {
3083
3346
  throw new Error("AI response missing script field");
3084
3347
  }
3348
+ const wordCount = script.trim().length === 0 ? 0 : script.trim().split(/\s+/).length;
3085
3349
  return {
3086
3350
  ...parsed,
3087
3351
  script,
3088
3352
  duration,
3089
- wordCount: script.split(" ").length
3353
+ wordCount
3090
3354
  };
3091
3355
  };
3092
3356
  var VideoScriptService = class {
@@ -3281,7 +3545,6 @@ var SeoService = class {
3281
3545
  analyzeKeywords(content, keywords) {
3282
3546
  const validatedContent = validateContentBody(content);
3283
3547
  const validatedKeywords = validateKeywordList(keywords);
3284
- void countWordsInContent(validatedContent);
3285
3548
  return validatedKeywords.map((keyword) => {
3286
3549
  const count = countKeywordOccurrences(validatedContent, keyword);
3287
3550
  const density = calculateKeywordDensity(validatedContent, keyword);
@@ -3376,68 +3639,20 @@ var ABTestService = class {
3376
3639
  });
3377
3640
  const scoreA = predictions[0]?.predictedEngagement ?? FALLBACK_ENGAGEMENT;
3378
3641
  const scoreB = predictions[1]?.predictedEngagement ?? FALLBACK_ENGAGEMENT;
3642
+ const isTie = scoreA === scoreB;
3379
3643
  const winner = selectHigherScoreVariantId(scoreA, scoreB, VARIANT_A_ID, VARIANT_B_ID);
3380
3644
  const improvement = calculateImprovementPercentage(scoreA, scoreB);
3381
3645
  return {
3382
3646
  winner,
3383
3647
  confidence: AB_TEST_DEFAULT_CONFIDENCE,
3384
- improvement: `+${improvement}% engagement`,
3385
- reasoning: winner === VARIANT_A_ID ? "Variant A has more engaging content" : "Variant B has more engaging content",
3648
+ improvement: isTie ? "0%" : `+${improvement}% engagement`,
3649
+ reasoning: isTie ? "Both variants have equal predicted engagement" : winner === VARIANT_A_ID ? "Variant A has more engaging content" : "Variant B has more engaging content",
3386
3650
  recommendations: [...DEFAULT_RECOMMENDATIONS]
3387
3651
  };
3388
3652
  }
3389
3653
  };
3390
3654
 
3391
- // src/content/infrastructure/utils/jsonParser.ts
3392
- function repairCommonJsonDefects(text) {
3393
- return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
3394
- }
3395
- function safeJSONParse(text, fallback, onError) {
3396
- try {
3397
- return JSON.parse(text);
3398
- } catch (error) {
3399
- try {
3400
- return JSON.parse(repairCommonJsonDefects(text));
3401
- } catch {
3402
- }
3403
- if (error instanceof SyntaxError) {
3404
- const positionMatch = error.message.match(/position (\d+)/);
3405
- const position = positionMatch ? parseInt(positionMatch[1], 10) : -1;
3406
- if (position > 0) {
3407
- text.substring(Math.max(0, position - 50), position + 50);
3408
- }
3409
- }
3410
- return fallback;
3411
- }
3412
- }
3413
- function extractJSON(text) {
3414
- const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
3415
- if (codeBlockMatch && codeBlockMatch[1]) {
3416
- const candidate = codeBlockMatch[1].trim();
3417
- if (candidate.startsWith("{") || candidate.startsWith("[")) {
3418
- return candidate;
3419
- }
3420
- }
3421
- const objectMatch = text.match(/\{[\s\S]*\}/);
3422
- if (objectMatch) {
3423
- return objectMatch[0];
3424
- }
3425
- const arrayMatch = text.match(/\[[\s\S]*\]/);
3426
- if (arrayMatch) {
3427
- return arrayMatch[0];
3428
- }
3429
- return null;
3430
- }
3431
- function parseAIResponse(response, fallback, onError) {
3432
- const json = extractJSON(response);
3433
- if (!json) {
3434
- return fallback;
3435
- }
3436
- return safeJSONParse(json, fallback);
3437
- }
3438
-
3439
3655
  // src/content/application/services/ImagePromptService.ts
3440
- var JSON_ARRAY_PATTERN = /\[[\s\S]*\]/;
3441
3656
  var ImagePromptService = class {
3442
3657
  constructor(executor) {
3443
3658
  this.executor = executor;
@@ -3451,10 +3666,11 @@ var ImagePromptService = class {
3451
3666
  async generateImagePromptsForBlog(blogContent) {
3452
3667
  const truncated = truncateForBlogPromptPreview(validateContentBody(blogContent));
3453
3668
  const prompt = PromptBuilder.blogImagePrompts(truncated);
3454
- const response = await this.executor.runText(prompt, { maxTokens: 500, temperature: 0.8 });
3455
- const jsonMatch = response.match(JSON_ARRAY_PATTERN);
3456
- if (!jsonMatch) return [];
3457
- return safeJSONParse(jsonMatch[0], []);
3669
+ const prompts = await this.executor.runJson(prompt, [], {
3670
+ maxTokens: 500,
3671
+ temperature: 0.8
3672
+ });
3673
+ return Array.isArray(prompts) ? prompts : [];
3458
3674
  }
3459
3675
  };
3460
3676
 
@@ -3498,22 +3714,27 @@ var ContentCalendarService = class {
3498
3714
  const validatedNiche = validateTopicText(niche);
3499
3715
  const validatedDays = validateCalendarDays(days);
3500
3716
  const prompt = PromptBuilder.contentCalendar(validatedNiche, validatedDays);
3501
- const data = await this.executor.runJson(prompt, [], {
3502
- maxTokens: estimateTokenBudgetForCalendarDays(validatedDays),
3503
- temperature: 0.8
3504
- });
3505
- return data.map(toCalendarEntry);
3717
+ const data = await this.executor.runJson(
3718
+ prompt,
3719
+ [],
3720
+ {
3721
+ maxTokens: estimateTokenBudgetForCalendarDays(validatedDays),
3722
+ temperature: 0.8
3723
+ }
3724
+ );
3725
+ const items = Array.isArray(data) ? data : Array.isArray(data?.calendar) ? data.calendar : Array.isArray(data?.entries) ? data.entries : [];
3726
+ return items.map(toCalendarEntry);
3506
3727
  }
3507
3728
  };
3508
3729
 
3509
3730
  // src/content/application/services/ProviderServices.ts
3510
3731
  var wrapProviderError = (operation, error) => {
3511
3732
  const message = error instanceof Error ? error.message : "Unknown error";
3512
- return new AIError2(`${operation} failed: ${message}`, "PROVIDER_ERROR", error);
3733
+ return new AIError(`${operation} failed: ${message}`, "PROVIDER_ERROR", error);
3513
3734
  };
3514
3735
  var ensureFactory = (factory, operation) => {
3515
3736
  if (factory.getAllProviders().length === 0) {
3516
- throw new AIError2(
3737
+ throw new AIError(
3517
3738
  `${operation} failed: No providers are registered. Provide a ProviderConfig with at least one enabled provider when constructing AIContentService.`,
3518
3739
  "PROVIDER_UNAVAILABLE"
3519
3740
  );
@@ -3550,6 +3771,54 @@ var MediaGenerationService = class {
3550
3771
  }
3551
3772
  };
3552
3773
 
3774
+ // src/content/infrastructure/utils/jsonParser.ts
3775
+ function repairCommonJsonDefects2(text) {
3776
+ return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
3777
+ }
3778
+ function safeJSONParse(text, fallback, onError) {
3779
+ try {
3780
+ return JSON.parse(text);
3781
+ } catch (error) {
3782
+ try {
3783
+ return JSON.parse(repairCommonJsonDefects2(text));
3784
+ } catch {
3785
+ }
3786
+ if (error instanceof SyntaxError) {
3787
+ const positionMatch = error.message.match(/position (\d+)/);
3788
+ const position = positionMatch ? parseInt(positionMatch[1], 10) : -1;
3789
+ if (position > 0) {
3790
+ text.substring(Math.max(0, position - 50), position + 50);
3791
+ }
3792
+ }
3793
+ return fallback;
3794
+ }
3795
+ }
3796
+ function extractJSON(text) {
3797
+ const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
3798
+ if (codeBlockMatch && codeBlockMatch[1]) {
3799
+ const candidate = codeBlockMatch[1].trim();
3800
+ if (candidate.startsWith("{") || candidate.startsWith("[")) {
3801
+ return candidate;
3802
+ }
3803
+ }
3804
+ const objectMatch = text.match(/\{[\s\S]*\}/);
3805
+ if (objectMatch) {
3806
+ return objectMatch[0];
3807
+ }
3808
+ const arrayMatch = text.match(/\[[\s\S]*\]/);
3809
+ if (arrayMatch) {
3810
+ return arrayMatch[0];
3811
+ }
3812
+ return null;
3813
+ }
3814
+ function parseAIResponse(response, fallback, onError) {
3815
+ const json = extractJSON(response);
3816
+ if (!json) {
3817
+ return fallback;
3818
+ }
3819
+ return safeJSONParse(json, fallback);
3820
+ }
3821
+
3553
3822
  // src/content/application/services/GenerationExecutor.ts
3554
3823
  var GenerationExecutor = class {
3555
3824
  constructor(generator) {
@@ -3566,7 +3835,7 @@ var GenerationExecutor = class {
3566
3835
  /** Usage-complete pass-through; requires a ProviderFactory engine. */
3567
3836
  runChat(request) {
3568
3837
  if (!(this.generator instanceof ProviderFactory)) {
3569
- throw new AIError2(
3838
+ throw new AIError(
3570
3839
  "runChat requires a ProviderFactory engine \u2014 construct AIContentService with a provider config, not a custom ITextGenerator",
3571
3840
  "PROVIDER_UNAVAILABLE"
3572
3841
  );
@@ -3580,6 +3849,13 @@ var isTextGenerator = (value) => typeof value === "object" && value !== null &&
3580
3849
  function toAIProviderConfig(config) {
3581
3850
  const legacy = config;
3582
3851
  const modern = config;
3852
+ const anthropic = modern.anthropic ?? (legacy.anthropic?.enabled ? {
3853
+ apiKey: legacy.anthropic.apiKey,
3854
+ baseUrl: legacy.anthropic.baseUrl,
3855
+ timeoutMs: legacy.anthropic.timeout,
3856
+ models: legacy.anthropic.models,
3857
+ effort: legacy.anthropic.effort
3858
+ } : void 0);
3583
3859
  const groq = modern.groq ?? (legacy.groq?.enabled ? {
3584
3860
  apiKey: legacy.groq.apiKey,
3585
3861
  baseUrl: legacy.groq.baseUrl,
@@ -3594,7 +3870,7 @@ function toAIProviderConfig(config) {
3594
3870
  priority: modern.priority,
3595
3871
  fallbackEnabled: modern.fallbackEnabled,
3596
3872
  defaultTier: modern.defaultTier,
3597
- anthropic: modern.anthropic,
3873
+ ...anthropic ? { anthropic } : {},
3598
3874
  ...groq ? { groq } : {},
3599
3875
  ...gemini ? { gemini } : {},
3600
3876
  ...fal ? { fal } : {},
@@ -3760,8 +4036,8 @@ function useAIContentContext(options) {
3760
4036
  callbacksRef.current?.onProgress?.(value);
3761
4037
  }, []);
3762
4038
  const reportError = useCallback((err, fallbackMessage) => {
3763
- const aiError = AIError2.from(err, "UNKNOWN");
3764
- const finalError = aiError.message ? aiError : new AIError2(fallbackMessage, "UNKNOWN", err);
4039
+ const aiError = AIError.from(err, "UNKNOWN");
4040
+ const finalError = aiError.message ? aiError : new AIError(fallbackMessage, "UNKNOWN", err);
3765
4041
  if (mountedRef.current) {
3766
4042
  setError(finalError.message);
3767
4043
  setErrorCode(finalError.code);
@@ -3787,7 +4063,7 @@ function useAIContentContext(options) {
3787
4063
  }
3788
4064
  return result;
3789
4065
  } catch (err) {
3790
- const aiError = AIError2.from(err, "UNKNOWN");
4066
+ const aiError = AIError.from(err, "UNKNOWN");
3791
4067
  if (aiError.message) {
3792
4068
  reportError(aiError, fallbackMessage);
3793
4069
  } else {
@@ -4351,10 +4627,13 @@ function useWizardFlow(options) {
4351
4627
  if (steps.length <= 1) return 0;
4352
4628
  return Math.round(currentIndex / (steps.length - 1) * 100);
4353
4629
  }, [currentIndex, steps.length]);
4630
+ const completedRef = useRef(false);
4354
4631
  const navigateTo = useCallback((to) => {
4355
4632
  setCurrentStep((prev) => {
4356
4633
  if (prev === to) return prev;
4357
- optionsRef.current.onStepChange?.(prev, to);
4634
+ queueMicrotask(() => {
4635
+ optionsRef.current.onStepChange?.(prev, to);
4636
+ });
4358
4637
  return to;
4359
4638
  });
4360
4639
  }, []);
@@ -4395,13 +4674,17 @@ function useWizardFlow(options) {
4395
4674
  [currentIndex, steps.length]
4396
4675
  );
4397
4676
  const reset = useCallback(() => {
4677
+ completedRef.current = false;
4398
4678
  setCurrentStep(optionsRef.current.initialStep);
4399
4679
  setWizardData({});
4400
4680
  setIsProcessing(false);
4401
4681
  }, []);
4402
4682
  useEffect(() => {
4403
- if (currentIndex === steps.length - 1 && steps.length > 0) {
4683
+ if (currentIndex === steps.length - 1 && steps.length > 0 && !completedRef.current) {
4684
+ completedRef.current = true;
4404
4685
  optionsRef.current.onComplete?.(wizardData);
4686
+ } else if (currentIndex < steps.length - 1) {
4687
+ completedRef.current = false;
4405
4688
  }
4406
4689
  }, [currentIndex, steps.length, wizardData]);
4407
4690
  return {
@@ -4630,21 +4913,17 @@ function useText(options = {}, deps) {
4630
4913
  ]
4631
4914
  );
4632
4915
  useEffect(() => {
4633
- const controller = new AbortController();
4634
- abortControllerRef.current = controller;
4635
4916
  return () => {
4636
- controller.abort();
4917
+ abortControllerRef.current?.abort();
4637
4918
  abortControllerRef.current = null;
4638
4919
  };
4639
4920
  }, []);
4640
4921
  const runWithState = useCallback(
4641
4922
  async (task) => {
4642
- const controller = abortControllerRef.current;
4643
- if (!controller) {
4644
- return null;
4645
- }
4923
+ abortControllerRef.current?.abort();
4924
+ const controller = new AbortController();
4925
+ abortControllerRef.current = controller;
4646
4926
  const { signal } = controller;
4647
- if (signal.aborted) return null;
4648
4927
  setIsLoading(true);
4649
4928
  setError(null);
4650
4929
  setResult(null);
@@ -4665,6 +4944,9 @@ function useText(options = {}, deps) {
4665
4944
  if (!signal.aborted) {
4666
4945
  setIsLoading(false);
4667
4946
  }
4947
+ if (abortControllerRef.current === controller) {
4948
+ abortControllerRef.current = null;
4949
+ }
4668
4950
  }
4669
4951
  },
4670
4952
  []
@@ -4729,6 +5011,7 @@ function useText(options = {}, deps) {
4729
5011
  );
4730
5012
  const cancel = useCallback(() => {
4731
5013
  abortControllerRef.current?.abort();
5014
+ abortControllerRef.current = null;
4732
5015
  setIsLoading(false);
4733
5016
  }, []);
4734
5017
  const reset = useCallback(() => {
@@ -4871,10 +5154,8 @@ function useChat(options, deps) {
4871
5154
  };
4872
5155
  }, [conversationId, surfaceError]);
4873
5156
  useEffect(() => {
4874
- const controller = new AbortController();
4875
- abortControllerRef.current = controller;
4876
5157
  return () => {
4877
- controller.abort();
5158
+ abortControllerRef.current?.abort();
4878
5159
  abortControllerRef.current = null;
4879
5160
  };
4880
5161
  }, []);
@@ -4915,9 +5196,11 @@ function useChat(options, deps) {
4915
5196
  });
4916
5197
  void persist(userMessage);
4917
5198
  callbacksRef.current.onMessageSent?.(userMessage);
5199
+ let streamingPlaceholderId = null;
4918
5200
  try {
4919
5201
  if (streamEnabled) {
4920
5202
  const placeholderId = chatIdGenerator.next("assistant");
5203
+ streamingPlaceholderId = placeholderId;
4921
5204
  const placeholder = {
4922
5205
  id: placeholderId,
4923
5206
  sender: "assistant",
@@ -4936,7 +5219,8 @@ function useChat(options, deps) {
4936
5219
  (m) => m.id === placeholderId ? { ...m, content: m.content + chunk } : m
4937
5220
  )
4938
5221
  );
4939
- }
5222
+ },
5223
+ controller.signal
4940
5224
  );
4941
5225
  const finalMessage = {
4942
5226
  ...placeholder,
@@ -4953,13 +5237,18 @@ function useChat(options, deps) {
4953
5237
  const aiResponse = await chatService.generateAIResponse(
4954
5238
  conversationId,
4955
5239
  trimmed,
4956
- contextForAI
5240
+ contextForAI,
5241
+ controller.signal
4957
5242
  );
4958
5243
  setMessages((prev) => [...prev, aiResponse]);
4959
5244
  void persist(aiResponse);
4960
5245
  callbacksRef.current.onAIResponse?.(aiResponse);
4961
5246
  return aiResponse;
4962
5247
  } catch (err) {
5248
+ if (streamingPlaceholderId) {
5249
+ const failedId = streamingPlaceholderId;
5250
+ setMessages((prev) => prev.filter((m) => m.id !== failedId));
5251
+ }
4963
5252
  surfaceError(err);
4964
5253
  return null;
4965
5254
  } finally {
@@ -4985,6 +5274,9 @@ function useChat(options, deps) {
4985
5274
  const lastUserMessage = [...currentMessages].reverse().find((m) => m.sender === "user");
4986
5275
  if (!lastUserMessage) return null;
4987
5276
  const trimmed = currentMessages.length > 0 && currentMessages[currentMessages.length - 1].sender === "assistant" ? currentMessages.slice(0, -1) : currentMessages;
5277
+ const controller = new AbortController();
5278
+ abortControllerRef.current?.abort();
5279
+ abortControllerRef.current = controller;
4988
5280
  setIsLoading(true);
4989
5281
  setError(null);
4990
5282
  setIsTyping(true);
@@ -4992,7 +5284,8 @@ function useChat(options, deps) {
4992
5284
  const aiResponse = await chatService.generateAIResponse(
4993
5285
  conversationId,
4994
5286
  lastUserMessage.content,
4995
- trimmed
5287
+ trimmed,
5288
+ controller.signal
4996
5289
  );
4997
5290
  setMessages((prev) => {
4998
5291
  const lastIsAI = prev.length > 0 && prev[prev.length - 1].sender === "assistant";
@@ -5008,6 +5301,9 @@ function useChat(options, deps) {
5008
5301
  } finally {
5009
5302
  setIsLoading(false);
5010
5303
  setIsTyping(false);
5304
+ if (abortControllerRef.current === controller) {
5305
+ abortControllerRef.current = null;
5306
+ }
5011
5307
  }
5012
5308
  }, [chatService, conversationId, isLoading, messages, persist, surfaceError]);
5013
5309
  const clearMessages = useCallback(() => {
@@ -5040,6 +5336,161 @@ function useChat(options, deps) {
5040
5336
  };
5041
5337
  }
5042
5338
 
5043
- export { useAIContent, useAIGeneration, useBlogGenerator, useCacheStats, useChat, useContentWizard, useImageWizard, useSocialContentGenerator, useText, useVideoWizard };
5339
+ // src/providers/groq/groq-container.ts
5340
+ var container = null;
5341
+ function getProvider() {
5342
+ return container;
5343
+ }
5344
+
5345
+ // src/hooks/use-groq.hook.ts
5346
+ function useGroq(options = {}, deps) {
5347
+ const textGeneration = deps?.textGeneration ?? getProvider()?.textGeneration;
5348
+ if (!textGeneration) {
5349
+ throw new Error(
5350
+ "useGroq requires a TextGenerationService dependency. Provide it via the second argument or wire it through configureProvider()."
5351
+ );
5352
+ }
5353
+ const [isLoading, setIsLoading] = useState(false);
5354
+ const [error, setError] = useState(null);
5355
+ const [result, setResult] = useState(null);
5356
+ const optionsRef = useRef(options);
5357
+ optionsRef.current = options;
5358
+ const abortControllerRef = useRef(null);
5359
+ const getOrCreateAbortController = useCallback(() => {
5360
+ if (!abortControllerRef.current || abortControllerRef.current.signal.aborted) {
5361
+ abortControllerRef.current = new AbortController();
5362
+ }
5363
+ return abortControllerRef.current;
5364
+ }, []);
5365
+ const stableConfig = useMemo(
5366
+ () => ({
5367
+ model: options.model,
5368
+ generationConfig: options.generationConfig
5369
+ }),
5370
+ [
5371
+ options.model,
5372
+ options.generationConfig?.temperature,
5373
+ options.generationConfig?.maxTokens,
5374
+ options.generationConfig?.topP
5375
+ ]
5376
+ );
5377
+ useEffect(() => {
5378
+ abortControllerRef.current = new AbortController();
5379
+ return () => {
5380
+ abortControllerRef.current?.abort();
5381
+ abortControllerRef.current = null;
5382
+ };
5383
+ }, []);
5384
+ const runWithState = useCallback(
5385
+ async (task) => {
5386
+ const controller = getOrCreateAbortController();
5387
+ const { signal } = controller;
5388
+ if (signal.aborted) return null;
5389
+ setIsLoading(true);
5390
+ setError(null);
5391
+ setResult(null);
5392
+ const currentOptions = optionsRef.current;
5393
+ currentOptions.onStart?.();
5394
+ try {
5395
+ const value = await task(signal);
5396
+ return value;
5397
+ } catch (err) {
5398
+ if (signal.aborted) {
5399
+ return null;
5400
+ }
5401
+ const message = resolveUserFriendlyMessage(err);
5402
+ setError(message);
5403
+ currentOptions.onError?.(message);
5404
+ throw err;
5405
+ } finally {
5406
+ if (!signal.aborted) {
5407
+ setIsLoading(false);
5408
+ }
5409
+ }
5410
+ },
5411
+ [getOrCreateAbortController]
5412
+ );
5413
+ const generate = useCallback(
5414
+ (prompt, config) => runWithState(async () => {
5415
+ const response = await textGeneration.generateCompletion(prompt, {
5416
+ model: stableConfig.model,
5417
+ generationConfig: {
5418
+ ...stableConfig.generationConfig,
5419
+ ...config
5420
+ }
5421
+ });
5422
+ setResult(response);
5423
+ optionsRef.current.onSuccess?.(response);
5424
+ return response;
5425
+ }),
5426
+ [runWithState, textGeneration, stableConfig]
5427
+ );
5428
+ const generateJSON = useCallback(
5429
+ (prompt, config) => runWithState(async () => {
5430
+ const response = await textGeneration.generateStructured(prompt, {
5431
+ model: stableConfig.model,
5432
+ generationConfig: {
5433
+ ...stableConfig.generationConfig,
5434
+ ...config
5435
+ },
5436
+ ...config?.schema !== void 0 ? { schema: config.schema } : {}
5437
+ });
5438
+ const json = JSON.stringify(response, null, 2);
5439
+ setResult(json);
5440
+ optionsRef.current.onSuccess?.(json);
5441
+ return response;
5442
+ }),
5443
+ [runWithState, textGeneration, stableConfig]
5444
+ );
5445
+ const stream = useCallback(
5446
+ (prompt, onChunk, config) => runWithState(async () => {
5447
+ const callbacks = {
5448
+ onChunk,
5449
+ onComplete: (text) => {
5450
+ setResult(text);
5451
+ optionsRef.current.onSuccess?.(text);
5452
+ }
5453
+ };
5454
+ return textGeneration.streamCompletion(
5455
+ prompt,
5456
+ callbacks,
5457
+ {
5458
+ model: stableConfig.model,
5459
+ generationConfig: {
5460
+ ...stableConfig.generationConfig,
5461
+ ...config
5462
+ }
5463
+ }
5464
+ );
5465
+ }),
5466
+ [runWithState, textGeneration, stableConfig]
5467
+ );
5468
+ const cancel = useCallback(() => {
5469
+ if (abortControllerRef.current) {
5470
+ abortControllerRef.current.abort();
5471
+ abortControllerRef.current = new AbortController();
5472
+ }
5473
+ setIsLoading(false);
5474
+ }, []);
5475
+ const reset = useCallback(() => {
5476
+ cancel();
5477
+ setError(null);
5478
+ setResult(null);
5479
+ }, [cancel]);
5480
+ const clearError = useCallback(() => setError(null), []);
5481
+ return {
5482
+ isLoading,
5483
+ error,
5484
+ result,
5485
+ generate,
5486
+ generateJSON,
5487
+ stream,
5488
+ cancel,
5489
+ reset,
5490
+ clearError
5491
+ };
5492
+ }
5493
+
5494
+ export { useAIContent, useAIGeneration, useBlogGenerator, useCacheStats, useChat, useContentWizard, useGroq, useImageWizard, useSocialContentGenerator, useText, useVideoWizard };
5044
5495
  //# sourceMappingURL=index.js.map
5045
5496
  //# sourceMappingURL=index.js.map