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