@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
@@ -75,37 +75,110 @@ var VIDEO_DURATION_OPTIONS = VIDEO_DURATION_OPTIONS_SECONDS;
75
75
  var getDimensionsForAspectRatio = resolveDimensionsForAspectRatio;
76
76
  var getRecommendedQuality = recommendQualityForAspectRatio;
77
77
 
78
- // src/content/domain/errors/AIErrors.ts
78
+ // src/core/errors/ai-error-types.ts
79
+ var AIErrorType = /* @__PURE__ */ ((AIErrorType2) => {
80
+ AIErrorType2["INVALID_API_KEY"] = "INVALID_API_KEY";
81
+ AIErrorType2["MISSING_CONFIG"] = "MISSING_CONFIG";
82
+ AIErrorType2["NETWORK_ERROR"] = "NETWORK_ERROR";
83
+ AIErrorType2["ABORT_ERROR"] = "ABORT_ERROR";
84
+ AIErrorType2["RATE_LIMIT_ERROR"] = "RATE_LIMIT_ERROR";
85
+ AIErrorType2["SERVER_ERROR"] = "SERVER_ERROR";
86
+ AIErrorType2["CLIENT_ERROR"] = "CLIENT_ERROR";
87
+ AIErrorType2["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
88
+ return AIErrorType2;
89
+ })(AIErrorType || {});
90
+
91
+ // src/core/errors/ai-error.ts
92
+ var KNOWN_ERROR_TYPES = new Set(Object.values(AIErrorType));
93
+ function mapCodeToType(code) {
94
+ switch (code) {
95
+ case "VALIDATION_ERROR":
96
+ case "CLIENT_ERROR":
97
+ return "CLIENT_ERROR" /* CLIENT_ERROR */;
98
+ case "INVALID_API_KEY":
99
+ return "INVALID_API_KEY" /* INVALID_API_KEY */;
100
+ case "MISSING_CONFIG":
101
+ case "PROVIDER_UNAVAILABLE":
102
+ return "MISSING_CONFIG" /* MISSING_CONFIG */;
103
+ case "PROVIDER_QUOTA_EXHAUSTED":
104
+ case "RATE_LIMIT_ERROR":
105
+ return "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */;
106
+ case "NETWORK_ERROR":
107
+ case "TIMEOUT":
108
+ return "NETWORK_ERROR" /* NETWORK_ERROR */;
109
+ case "ABORT_ERROR":
110
+ case "ABORTED":
111
+ return "ABORT_ERROR" /* ABORT_ERROR */;
112
+ case "SERVER_ERROR":
113
+ case "PROVIDER_ERROR":
114
+ case "NO_RESULT":
115
+ return "SERVER_ERROR" /* SERVER_ERROR */;
116
+ default:
117
+ return "UNKNOWN_ERROR" /* UNKNOWN_ERROR */;
118
+ }
119
+ }
120
+ function mapMessageToCode(message, fallback = "UNKNOWN") {
121
+ const lower = message.toLowerCase();
122
+ if (lower.includes("validation")) return "VALIDATION_ERROR";
123
+ if (lower.includes("quota")) return "PROVIDER_QUOTA_EXHAUSTED";
124
+ if (lower.includes("unavailable")) return "PROVIDER_UNAVAILABLE";
125
+ if (lower.includes("timeout") || lower.includes("aborted")) {
126
+ return lower.includes("aborted") ? "ABORTED" : "TIMEOUT";
127
+ }
128
+ if (lower.includes("parse") || lower.includes("json")) return "PARSE_ERROR";
129
+ if (lower.includes("no result")) return "NO_RESULT";
130
+ if (lower.includes("network") || lower.includes("fetch")) return "NETWORK_ERROR";
131
+ return fallback;
132
+ }
79
133
  var AIError = class _AIError extends Error {
134
+ type;
80
135
  code;
81
136
  cause;
82
- constructor(message, code, cause) {
83
- super(message);
137
+ metadata;
138
+ constructor(typeOrMessage, messageOrCode, cause, metadata = {}) {
139
+ let effectiveType;
140
+ let effectiveMessage;
141
+ let effectiveCode;
142
+ if (KNOWN_ERROR_TYPES.has(typeOrMessage)) {
143
+ effectiveType = typeOrMessage;
144
+ effectiveMessage = messageOrCode ?? "";
145
+ effectiveCode = effectiveType;
146
+ } else {
147
+ effectiveMessage = typeOrMessage;
148
+ effectiveCode = messageOrCode ?? "UNKNOWN";
149
+ effectiveType = mapCodeToType(effectiveCode);
150
+ }
151
+ super(effectiveMessage);
84
152
  this.name = "AIError";
85
- this.code = code;
153
+ this.type = effectiveType;
154
+ this.code = effectiveCode;
86
155
  this.cause = cause;
156
+ this.metadata = metadata;
87
157
  }
88
158
  static from(error, fallbackCode = "UNKNOWN") {
89
159
  if (error instanceof _AIError) return error;
90
160
  if (error instanceof Error) {
91
- return new _AIError(error.message, mapMessageToCode(error.message, fallbackCode), error);
161
+ const code = mapMessageToCode(error.message, fallbackCode);
162
+ return new _AIError(error.message, code, error);
92
163
  }
93
164
  return new _AIError(String(error), fallbackCode, error);
94
165
  }
95
- };
96
- function mapMessageToCode(message, fallback) {
97
- const lower = message.toLowerCase();
98
- if (lower.includes("validation")) return "VALIDATION_ERROR";
99
- if (lower.includes("quota")) return "PROVIDER_QUOTA_EXHAUSTED";
100
- if (lower.includes("unavailable")) return "PROVIDER_UNAVAILABLE";
101
- if (lower.includes("timeout") || lower.includes("aborted")) {
102
- return lower.includes("aborted") ? "ABORTED" : "TIMEOUT";
166
+ toString() {
167
+ const statusPart = typeof this.metadata.status === "number" ? ` (HTTP ${this.metadata.status})` : "";
168
+ return `${this.name} [${this.type}]: ${this.message}${statusPart}`;
103
169
  }
104
- if (lower.includes("parse") || lower.includes("json")) return "PARSE_ERROR";
105
- if (lower.includes("no result")) return "NO_RESULT";
106
- if (lower.includes("network") || lower.includes("fetch")) return "NETWORK_ERROR";
107
- return fallback;
108
- }
170
+ toJSON() {
171
+ return {
172
+ name: this.name,
173
+ type: this.type,
174
+ code: this.code,
175
+ message: this.message,
176
+ metadata: this.metadata
177
+ };
178
+ }
179
+ };
180
+
181
+ // src/content/domain/errors/AIErrors.ts
109
182
  var ValidationError = class extends AIError {
110
183
  constructor(message) {
111
184
  super(message, "VALIDATION_ERROR");
@@ -125,29 +198,11 @@ var ProviderQuotaExhaustedError = class extends AIError {
125
198
  }
126
199
  };
127
200
 
128
- // src/core/errors/ai-error.ts
129
- var AIError2 = class extends Error {
130
- type;
131
- cause;
132
- metadata;
201
+ // src/core/errors/groq-error.ts
202
+ var GroqError = class extends AIError {
133
203
  constructor(type, message, cause, metadata = {}) {
134
- super(message);
135
- this.name = "AIError";
136
- this.type = type;
137
- this.cause = cause;
138
- this.metadata = metadata;
139
- }
140
- toString() {
141
- const statusPart = typeof this.metadata.status === "number" ? ` (HTTP ${this.metadata.status})` : "";
142
- return `${this.name} [${this.type}]: ${this.message}${statusPart}`;
143
- }
144
- toJSON() {
145
- return {
146
- name: this.name,
147
- type: this.type,
148
- message: this.message,
149
- metadata: this.metadata
150
- };
204
+ super(type, message, cause, metadata);
205
+ this.name = "GroqError";
151
206
  }
152
207
  };
153
208
 
@@ -293,7 +348,7 @@ var ProviderFactory = class {
293
348
  }
294
349
  }
295
350
  }
296
- const failure = new AIError2(
351
+ const failure = new AIError(
297
352
  "SERVER_ERROR" /* SERVER_ERROR */,
298
353
  `All providers failed for capability 'text'`,
299
354
  lastError instanceof Error ? lastError : void 0
@@ -307,12 +362,16 @@ var ProviderFactory = class {
307
362
  let wake = null;
308
363
  let finished = false;
309
364
  let failure;
365
+ const controller = new AbortController();
366
+ const onAbort = () => controller.abort();
367
+ request.signal?.addEventListener("abort", onAbort);
368
+ const effectiveRequest = { ...request, signal: controller.signal };
310
369
  const notify = () => {
311
370
  const resolve = wake;
312
371
  wake = null;
313
372
  resolve?.();
314
373
  };
315
- const completion = this.chatStream(request, {
374
+ const completion = this.chatStream(effectiveRequest, {
316
375
  onChunk: (chunk) => {
317
376
  queue.push(chunk);
318
377
  notify();
@@ -328,18 +387,23 @@ var ProviderFactory = class {
328
387
  notify();
329
388
  }
330
389
  );
331
- while (!finished || queue.length > 0) {
332
- if (queue.length === 0 && !finished) {
333
- await new Promise((resolve) => {
334
- wake = resolve;
335
- });
336
- }
337
- while (queue.length > 0) {
338
- yield queue.shift();
390
+ try {
391
+ while (!finished || queue.length > 0) {
392
+ if (queue.length === 0 && !finished) {
393
+ await new Promise((resolve) => {
394
+ wake = resolve;
395
+ });
396
+ }
397
+ while (queue.length > 0) {
398
+ yield queue.shift();
399
+ }
400
+ if (failure) throw failure;
339
401
  }
340
- if (failure) throw failure;
402
+ await completion;
403
+ } finally {
404
+ controller.abort();
405
+ request.signal?.removeEventListener("abort", onAbort);
341
406
  }
342
- await completion;
343
407
  }
344
408
  async generateJson(request) {
345
409
  const effective = request.model || request.tier ? request : { ...request, tier: this.defaultTier };
@@ -447,7 +511,7 @@ var ProviderFactory = class {
447
511
  }
448
512
  }
449
513
  }
450
- throw new AIError2(
514
+ throw new AIError(
451
515
  "SERVER_ERROR" /* SERVER_ERROR */,
452
516
  `All providers failed for capability '${capability}'`,
453
517
  lastError instanceof Error ? lastError : void 0
@@ -476,7 +540,7 @@ var ProviderFactory = class {
476
540
  }
477
541
  }
478
542
  noProviderError(capability) {
479
- return new AIError2(
543
+ return new AIError(
480
544
  "MISSING_CONFIG" /* MISSING_CONFIG */,
481
545
  `No provider registered for capability '${capability}' \u2014 create one with createAI() and include its config block`
482
546
  );
@@ -486,15 +550,22 @@ var ProviderFactory = class {
486
550
  // src/core/constants/protocol.constants.ts
487
551
  var SSE_PREFIX = "data: ";
488
552
  var SSE_DONE_MARKER = "[DONE]";
489
- var JSON_CODE_BLOCK_PATTERN = /```json\n([\s\S]*?)\n```/;
553
+ var JSON_CODE_BLOCK_PATTERN = /```(?:json)?\s*\n?([\s\S]*?)\n?```/;
490
554
  var JSON_OBJECT_PATTERN = /\{[\s\S]*\}/;
555
+ var JSON_ARRAY_PATTERN = /\[[\s\S]*\]/;
491
556
 
492
557
  // src/core/parsers/sse-lines.ts
493
558
  function isSseDataLine(line) {
494
- return line.startsWith(SSE_PREFIX);
559
+ return line.startsWith(SSE_PREFIX) || line.startsWith("data:");
495
560
  }
496
561
  function extractSseDataPayload(line) {
497
- return line.slice(SSE_PREFIX.length);
562
+ if (line.startsWith(SSE_PREFIX)) {
563
+ return line.slice(SSE_PREFIX.length);
564
+ }
565
+ if (line.startsWith("data:")) {
566
+ return line.slice(5);
567
+ }
568
+ return line;
498
569
  }
499
570
  function isSseDoneMarker(payload) {
500
571
  return payload === SSE_DONE_MARKER;
@@ -503,7 +574,7 @@ function isSseDoneMarker(payload) {
503
574
  // src/core/parsers/json-content-extractor.ts
504
575
  function extractJsonFromCodeFence(content) {
505
576
  const match = content.match(JSON_CODE_BLOCK_PATTERN);
506
- if (match && match[1]) return match[1];
577
+ if (match && match[1]) return match[1].trim();
507
578
  return null;
508
579
  }
509
580
  function extractJsonObject(content) {
@@ -511,8 +582,27 @@ function extractJsonObject(content) {
511
582
  if (match && match[0]) return match[0];
512
583
  return null;
513
584
  }
585
+ function extractJsonArray(content) {
586
+ const match = content.match(JSON_ARRAY_PATTERN);
587
+ if (match && match[0]) return match[0];
588
+ return null;
589
+ }
590
+ function repairCommonJsonDefects(text) {
591
+ return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
592
+ }
514
593
  function extractJsonCandidate(content) {
515
- return extractJsonFromCodeFence(content) ?? extractJsonObject(content) ?? content;
594
+ const fromFence = extractJsonFromCodeFence(content);
595
+ if (fromFence) return fromFence;
596
+ const objectIndex = content.indexOf("{");
597
+ const arrayIndex = content.indexOf("[");
598
+ if (objectIndex !== -1 && (arrayIndex === -1 || objectIndex < arrayIndex)) {
599
+ const obj = extractJsonObject(content);
600
+ if (obj) return obj;
601
+ } else if (arrayIndex !== -1) {
602
+ const arr = extractJsonArray(content);
603
+ if (arr) return arr;
604
+ }
605
+ return content.trim();
516
606
  }
517
607
 
518
608
  // src/core/parsers/json-response.parser.ts
@@ -521,12 +611,17 @@ var JsonResponseParser = class {
521
611
  const candidate = extractJsonCandidate(rawContent);
522
612
  try {
523
613
  return JSON.parse(candidate);
524
- } catch (error) {
525
- throw new AIError2(
526
- "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
527
- "Failed to parse structured response as JSON",
528
- error instanceof Error ? error : void 0
529
- );
614
+ } catch {
615
+ try {
616
+ return JSON.parse(repairCommonJsonDefects(candidate));
617
+ } catch (error) {
618
+ throw new AIError(
619
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
620
+ "Failed to parse structured response as JSON",
621
+ error instanceof Error ? error : void 0,
622
+ { rawContent }
623
+ );
624
+ }
530
625
  }
531
626
  }
532
627
  };
@@ -652,7 +747,7 @@ var AnthropicHttpTransport = class {
652
747
  });
653
748
  if (!response.ok) await this.throwFromErrorResponse(response);
654
749
  if (!response.body) {
655
- throw new AIError2("NETWORK_ERROR" /* NETWORK_ERROR */, "Response body is null");
750
+ throw new AIError("NETWORK_ERROR" /* NETWORK_ERROR */, "Response body is null");
656
751
  }
657
752
  return response.body;
658
753
  } catch (error) {
@@ -685,7 +780,7 @@ var AnthropicHttpTransport = class {
685
780
  status: response.status,
686
781
  url: response.url
687
782
  };
688
- throw new AIError2(
783
+ throw new AIError(
689
784
  classifyHttpStatus(response.status),
690
785
  errorMessage,
691
786
  void 0,
@@ -693,24 +788,24 @@ var AnthropicHttpTransport = class {
693
788
  );
694
789
  }
695
790
  normalizeError(error) {
696
- if (error instanceof AIError2) return error;
791
+ if (error instanceof AIError) return error;
697
792
  if (error instanceof Error) {
698
793
  if (error.name === "AbortError") {
699
- return new AIError2(
794
+ return new AIError(
700
795
  "ABORT_ERROR" /* ABORT_ERROR */,
701
796
  "Request was aborted by the client",
702
797
  error
703
798
  );
704
799
  }
705
800
  if (error.name === "TypeError" && error.message.toLowerCase().includes("network")) {
706
- return new AIError2(
801
+ return new AIError(
707
802
  "NETWORK_ERROR" /* NETWORK_ERROR */,
708
803
  "Network error: Unable to connect to the AI service",
709
804
  error
710
805
  );
711
806
  }
712
807
  }
713
- return new AIError2(
808
+ return new AIError(
714
809
  "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
715
810
  error instanceof Error ? error.message : "Unknown error occurred",
716
811
  error instanceof Error ? error : void 0
@@ -840,11 +935,14 @@ async function consumeAnthropicStream(stream, callbacks) {
840
935
  break;
841
936
  case "message_stop":
842
937
  break;
843
- case "error":
844
- throw new AIError2(
845
- event.error.type === "rate_limit_error" ? "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ : "SERVER_ERROR" /* SERVER_ERROR */,
938
+ case "error": {
939
+ const errType = event.error.type;
940
+ 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 */;
941
+ throw new AIError(
942
+ mappedType,
846
943
  event.error.message || "Anthropic stream failed mid-flight"
847
944
  );
945
+ }
848
946
  }
849
947
  };
850
948
  try {
@@ -859,8 +957,8 @@ async function consumeAnthropicStream(stream, callbacks) {
859
957
  buffer += decoder.decode();
860
958
  if (buffer.length > 0) consumeLine(buffer);
861
959
  } catch (error) {
862
- if (error instanceof AIError2) throw error;
863
- throw new AIError2(
960
+ if (error instanceof AIError) throw error;
961
+ throw new AIError(
864
962
  "NETWORK_ERROR" /* NETWORK_ERROR */,
865
963
  "Stream was interrupted",
866
964
  error instanceof Error ? error : void 0
@@ -922,7 +1020,7 @@ var AnthropicProvider = class {
922
1020
  const messages = [...request.messages];
923
1021
  const lastUserIndex = findLastIndex(messages, (m) => m.role === "user");
924
1022
  if (lastUserIndex === -1) {
925
- throw new AIError2(
1023
+ throw new AIError(
926
1024
  "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
927
1025
  "generateStructured requires at least one user message"
928
1026
  );
@@ -977,7 +1075,7 @@ var AnthropicProvider = class {
977
1075
  finishReason
978
1076
  };
979
1077
  }
980
- throw new AIError2(
1078
+ throw new AIError(
981
1079
  "SERVER_ERROR" /* SERVER_ERROR */,
982
1080
  "No content generated from Anthropic API",
983
1081
  void 0,
@@ -1182,7 +1280,7 @@ var RetryManager = class {
1182
1280
  }
1183
1281
  shouldRetry(error, options) {
1184
1282
  if (options.shouldRetry) return options.shouldRetry(error);
1185
- if (error instanceof AIError2) {
1283
+ if (error instanceof AIError) {
1186
1284
  return isRetryableErrorType(error.type, DEFAULT_ERROR_CLASSIFICATION_RULE);
1187
1285
  }
1188
1286
  const lower = error.message.toLowerCase();
@@ -1304,6 +1402,27 @@ var RequestDeduplicator = class {
1304
1402
  }
1305
1403
  };
1306
1404
 
1405
+ // src/core/utils/api-key-validator.ts
1406
+ var DEFAULT_API_KEY_RULE = {
1407
+ minLength: 10
1408
+ };
1409
+ function validateApiKey(apiKey, rule = DEFAULT_API_KEY_RULE) {
1410
+ if (!apiKey) {
1411
+ return { valid: false, reason: "API key is required" };
1412
+ }
1413
+ const trimmed = apiKey.trim();
1414
+ if (trimmed.length === 0) {
1415
+ return { valid: false, reason: "API key cannot be empty" };
1416
+ }
1417
+ if (trimmed.length < rule.minLength) {
1418
+ return {
1419
+ valid: false,
1420
+ reason: `API key must be at least ${rule.minLength} characters`
1421
+ };
1422
+ }
1423
+ return { valid: true };
1424
+ }
1425
+
1307
1426
  // src/providers/groq/groq.constants.ts
1308
1427
  var GROQ_MODELS = {
1309
1428
  LLAMA_3_1_8B_INSTANT: "llama-3.1-8b-instant",
@@ -1320,11 +1439,13 @@ var DEFAULT_GENERATION_CONFIG = {
1320
1439
  topP: 1
1321
1440
  };
1322
1441
  var GROQ_BASE_URL = "https://api.groq.com/openai/v1";
1442
+ var DEFAULT_BASE_URL = GROQ_BASE_URL;
1323
1443
  var CHAT_COMPLETIONS_PATH = "/chat/completions";
1324
1444
  var GROQ_TIMEOUTS = {
1325
1445
  DEFAULT: 3e4,
1326
1446
  STREAMING: 6e4
1327
1447
  };
1448
+ var TIMEOUTS = GROQ_TIMEOUTS;
1328
1449
  var HTTP_HEADERS = {
1329
1450
  CONTENT_TYPE: "Content-Type",
1330
1451
  APPLICATION_JSON: "application/json",
@@ -1383,13 +1504,65 @@ var GroqChatRequestBuilder = class _GroqChatRequestBuilder {
1383
1504
  }
1384
1505
  };
1385
1506
 
1507
+ // src/providers/groq/groq-config-store.ts
1508
+ var GroqConfigStore = class {
1509
+ config = null;
1510
+ initialize(config) {
1511
+ const validation = validateApiKey(config.apiKey, { minLength: 1 });
1512
+ if (!validation.valid) {
1513
+ throw new GroqError("INVALID_API_KEY" /* INVALID_API_KEY */, validation.reason ?? "Invalid API key");
1514
+ }
1515
+ const normalized = {
1516
+ apiKey: config.apiKey.trim(),
1517
+ baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
1518
+ timeoutMs: config.timeoutMs ?? TIMEOUTS.DEFAULT,
1519
+ ...config.models !== void 0 ? { models: config.models } : {},
1520
+ ...config.textModel !== void 0 ? { textModel: config.textModel } : {}
1521
+ };
1522
+ this.config = normalized;
1523
+ return normalized;
1524
+ }
1525
+ get() {
1526
+ if (!this.config) {
1527
+ throw new GroqError(
1528
+ "MISSING_CONFIG" /* MISSING_CONFIG */,
1529
+ "Client not initialized. Call configureProvider first."
1530
+ );
1531
+ }
1532
+ return this.config;
1533
+ }
1534
+ getOrNull() {
1535
+ return this.config;
1536
+ }
1537
+ isInitialized() {
1538
+ return this.config !== null;
1539
+ }
1540
+ setApiKey(apiKey) {
1541
+ const current = this.get();
1542
+ const validation = validateApiKey(apiKey, { minLength: 1 });
1543
+ if (!validation.valid) {
1544
+ throw new GroqError("INVALID_API_KEY" /* INVALID_API_KEY */, validation.reason ?? "Invalid API key");
1545
+ }
1546
+ this.config = { ...current, apiKey: apiKey.trim() };
1547
+ }
1548
+ reset() {
1549
+ this.config = null;
1550
+ }
1551
+ };
1552
+
1386
1553
  // src/providers/groq/groq-http-transport.ts
1387
1554
  var GroqHttpTransport = class {
1388
- constructor(config) {
1389
- this.config = config;
1555
+ constructor(configSource) {
1556
+ this.configSource = configSource;
1390
1557
  }
1391
- config;
1558
+ configSource;
1392
1559
  pendingAbortControllers = /* @__PURE__ */ new Set();
1560
+ getConfig() {
1561
+ if ("get" in this.configSource && typeof this.configSource.get === "function") {
1562
+ return this.configSource.get();
1563
+ }
1564
+ return this.configSource;
1565
+ }
1393
1566
  cancelPendingRequests() {
1394
1567
  for (const controller of this.pendingAbortControllers) {
1395
1568
  controller.abort();
@@ -1404,7 +1577,7 @@ var GroqHttpTransport = class {
1404
1577
  }
1405
1578
  async streamChatCompletion(request, options = {}) {
1406
1579
  const url = `${this.baseUrl()}${CHAT_COMPLETIONS_PATH}`;
1407
- const timeout = this.config.timeoutMs ?? GROQ_TIMEOUTS.STREAMING;
1580
+ const timeout = this.getConfig().timeoutMs ?? GROQ_TIMEOUTS.STREAMING;
1408
1581
  const controller = new AbortController();
1409
1582
  const timeoutId = setTimeout(() => controller.abort(), timeout);
1410
1583
  const onExternalAbort = () => controller.abort();
@@ -1419,7 +1592,7 @@ var GroqHttpTransport = class {
1419
1592
  });
1420
1593
  if (!response.ok) await this.throwFromErrorResponse(response);
1421
1594
  if (!response.body) {
1422
- throw new AIError2("NETWORK_ERROR" /* NETWORK_ERROR */, "Response body is null");
1595
+ throw new AIError("NETWORK_ERROR" /* NETWORK_ERROR */, "Response body is null");
1423
1596
  }
1424
1597
  return response.body;
1425
1598
  } catch (error) {
@@ -1431,11 +1604,11 @@ var GroqHttpTransport = class {
1431
1604
  }
1432
1605
  }
1433
1606
  baseUrl() {
1434
- return this.config.baseUrl ?? GROQ_BASE_URL;
1607
+ return this.getConfig().baseUrl ?? GROQ_BASE_URL;
1435
1608
  }
1436
1609
  async request(endpoint, body) {
1437
1610
  const url = `${this.baseUrl()}${endpoint}`;
1438
- const timeout = this.config.timeoutMs ?? GROQ_TIMEOUTS.DEFAULT;
1611
+ const timeout = this.getConfig().timeoutMs ?? GROQ_TIMEOUTS.DEFAULT;
1439
1612
  const controller = new AbortController();
1440
1613
  const timeoutId = setTimeout(() => controller.abort(), timeout);
1441
1614
  this.pendingAbortControllers.add(controller);
@@ -1458,7 +1631,7 @@ var GroqHttpTransport = class {
1458
1631
  buildHeaders() {
1459
1632
  return {
1460
1633
  [HTTP_HEADERS.CONTENT_TYPE]: HTTP_HEADERS.APPLICATION_JSON,
1461
- [HTTP_HEADERS.AUTHORIZATION]: `${HTTP_HEADERS.BEARER_PREFIX}${this.config.apiKey}`
1634
+ [HTTP_HEADERS.AUTHORIZATION]: `${HTTP_HEADERS.BEARER_PREFIX}${this.getConfig().apiKey}`
1462
1635
  };
1463
1636
  }
1464
1637
  async throwFromErrorResponse(response) {
@@ -1467,7 +1640,7 @@ var GroqHttpTransport = class {
1467
1640
  status: response.status,
1468
1641
  url: response.url
1469
1642
  };
1470
- throw new AIError2(
1643
+ throw new AIError(
1471
1644
  classifyHttpStatus(response.status),
1472
1645
  errorMessage,
1473
1646
  void 0,
@@ -1475,24 +1648,24 @@ var GroqHttpTransport = class {
1475
1648
  );
1476
1649
  }
1477
1650
  normalizeError(error) {
1478
- if (error instanceof AIError2) return error;
1651
+ if (error instanceof AIError) return error;
1479
1652
  if (error instanceof Error) {
1480
1653
  if (error.name === "AbortError") {
1481
- return new AIError2(
1654
+ return new AIError(
1482
1655
  "ABORT_ERROR" /* ABORT_ERROR */,
1483
1656
  "Request was aborted by the client",
1484
1657
  error
1485
1658
  );
1486
1659
  }
1487
1660
  if (error.name === "TypeError" && error.message.toLowerCase().includes("network")) {
1488
- return new AIError2(
1661
+ return new AIError(
1489
1662
  "NETWORK_ERROR" /* NETWORK_ERROR */,
1490
1663
  "Network error: Unable to connect to the AI service",
1491
1664
  error
1492
1665
  );
1493
1666
  }
1494
1667
  }
1495
- return new AIError2(
1668
+ return new AIError(
1496
1669
  "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
1497
1670
  error instanceof Error ? error.message : "Unknown error occurred",
1498
1671
  error instanceof Error ? error : void 0
@@ -1560,21 +1733,40 @@ var CompositeExecutor = class {
1560
1733
 
1561
1734
  // src/providers/groq/groq-http-client.ts
1562
1735
  var GroqHttpClient = class {
1736
+ configStore;
1563
1737
  transport;
1564
1738
  executor;
1565
- constructor(config, deps = {}) {
1566
- this.transport = deps.transport ?? new GroqHttpTransport(config);
1739
+ constructor(configOrDeps, maybeDeps = {}) {
1740
+ const isConfig = typeof configOrDeps === "object" && configOrDeps !== null && "apiKey" in configOrDeps && typeof configOrDeps.apiKey === "string";
1741
+ const deps = isConfig ? maybeDeps : configOrDeps ?? {};
1742
+ this.configStore = deps.configStore ?? new GroqConfigStore();
1743
+ if (isConfig) {
1744
+ this.configStore.initialize(configOrDeps);
1745
+ }
1746
+ this.transport = deps.transport ?? new GroqHttpTransport(this.configStore);
1567
1747
  const retryManager2 = deps.retryManager ?? new RetryManager();
1568
1748
  const queue = deps.queue ?? new RequestQueue();
1569
1749
  const deduplicator = deps.deduplicator ?? new RequestDeduplicator();
1570
1750
  this.executor = new CompositeExecutor([
1571
1751
  new DedupeExecutor(deduplicator, {
1572
- generateKey: (request) => deduplicatorKey(request, config.apiKey)
1752
+ generateKey: (request) => deduplicatorKey(request, this.configStore.isInitialized() ? this.configStore.get().apiKey : "")
1573
1753
  }),
1574
1754
  new RetryExecutor(retryManager2),
1575
1755
  new QueueExecutor(queue, QUEUE_PRIORITY_USER_REQUEST)
1576
1756
  ]);
1577
1757
  }
1758
+ initialize(config) {
1759
+ this.configStore.initialize(config);
1760
+ }
1761
+ isInitialized() {
1762
+ return this.configStore.isInitialized();
1763
+ }
1764
+ getApiKey() {
1765
+ return this.configStore.get().apiKey;
1766
+ }
1767
+ setApiKey(apiKey) {
1768
+ this.configStore.setApiKey(apiKey);
1769
+ }
1578
1770
  cancelPendingRequests() {
1579
1771
  this.transport.cancelPendingRequests();
1580
1772
  }
@@ -1587,6 +1779,10 @@ var GroqHttpClient = class {
1587
1779
  streamChatCompletion(request, options) {
1588
1780
  return this.transport.streamChatCompletion(request, options);
1589
1781
  }
1782
+ reset() {
1783
+ this.transport.cancelPendingRequests();
1784
+ this.configStore.reset();
1785
+ }
1590
1786
  };
1591
1787
  function deduplicatorKey(request, apiKey) {
1592
1788
  const { model, messages, stream: _stream, ...rest } = request;
@@ -1634,6 +1830,13 @@ async function consumeGroqStream(stream, callbacks) {
1634
1830
  } catch {
1635
1831
  return;
1636
1832
  }
1833
+ const errorBody = chunk.error;
1834
+ if (errorBody) {
1835
+ throw new AIError(
1836
+ errorBody.message?.toLowerCase().includes("rate limit") ? "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ : "SERVER_ERROR" /* SERVER_ERROR */,
1837
+ errorBody.message || "Groq stream failed mid-flight"
1838
+ );
1839
+ }
1637
1840
  if (chunk.model) model = chunk.model;
1638
1841
  const choice = chunk.choices?.[0];
1639
1842
  const delta = choice?.delta?.content;
@@ -1648,13 +1851,6 @@ async function consumeGroqStream(stream, callbacks) {
1648
1851
  if (tailUsage) {
1649
1852
  usage = mapGroqUsage(tailUsage);
1650
1853
  }
1651
- const errorBody = chunk.error;
1652
- if (errorBody) {
1653
- throw new AIError2(
1654
- "SERVER_ERROR" /* SERVER_ERROR */,
1655
- errorBody.message || "Groq stream failed mid-flight"
1656
- );
1657
- }
1658
1854
  };
1659
1855
  try {
1660
1856
  while (true) {
@@ -1668,8 +1864,8 @@ async function consumeGroqStream(stream, callbacks) {
1668
1864
  buffer += decoder.decode();
1669
1865
  if (buffer.length > 0) consumeLine(buffer);
1670
1866
  } catch (error) {
1671
- if (error instanceof AIError2) throw error;
1672
- throw new AIError2(
1867
+ if (error instanceof AIError) throw error;
1868
+ throw new AIError(
1673
1869
  "NETWORK_ERROR" /* NETWORK_ERROR */,
1674
1870
  "Stream was interrupted",
1675
1871
  error instanceof Error ? error : void 0
@@ -1727,7 +1923,7 @@ var GroqProvider = class {
1727
1923
  const choice = response.choices?.[0];
1728
1924
  const text = choice?.message?.content ?? "";
1729
1925
  if (text.length === 0) {
1730
- throw new AIError2(
1926
+ throw new AIError(
1731
1927
  "SERVER_ERROR" /* SERVER_ERROR */,
1732
1928
  "No content generated from Groq API",
1733
1929
  void 0,
@@ -1749,7 +1945,7 @@ var GroqProvider = class {
1749
1945
  const messages = toGroqMessages(request.messages, request.systemPrompt);
1750
1946
  const lastUserIndex = findLastIndex2(messages, (m) => m.role === "user");
1751
1947
  if (lastUserIndex === -1) {
1752
- throw new AIError2(
1948
+ throw new AIError(
1753
1949
  "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
1754
1950
  "generateStructured requires at least one user message"
1755
1951
  );
@@ -1763,7 +1959,7 @@ var GroqProvider = class {
1763
1959
  const choice = response.choices?.[0];
1764
1960
  const text = choice?.message?.content ?? "";
1765
1961
  if (text.length === 0) {
1766
- throw new AIError2(
1962
+ throw new AIError(
1767
1963
  "SERVER_ERROR" /* SERVER_ERROR */,
1768
1964
  "No content generated from Groq API",
1769
1965
  void 0,
@@ -1790,7 +1986,7 @@ var GroqProvider = class {
1790
1986
  try {
1791
1987
  const outcome = await consumeGroqStream(streamBody, callbacks);
1792
1988
  if (outcome.text.length === 0) {
1793
- throw new AIError2(
1989
+ throw new AIError(
1794
1990
  "SERVER_ERROR" /* SERVER_ERROR */,
1795
1991
  "No content generated from Groq API",
1796
1992
  void 0,
@@ -1974,7 +2170,9 @@ var GeminiProvider = class extends BaseProvider {
1974
2170
  headers: this.authHeaders()
1975
2171
  }
1976
2172
  );
1977
- return response.ok ? "healthy" : "unavailable";
2173
+ if (response.ok) return "healthy";
2174
+ if (response.status === 401 || response.status === 403) return "unavailable";
2175
+ return "degraded";
1978
2176
  } catch {
1979
2177
  return "unavailable";
1980
2178
  }
@@ -1985,6 +2183,8 @@ var GeminiProvider = class extends BaseProvider {
1985
2183
  async generate(request) {
1986
2184
  try {
1987
2185
  const model = request.model ?? this.resolveModel(request.tier ?? "balanced");
2186
+ const systemMessages = request.messages.filter((m) => m.role === "system");
2187
+ const systemPrompt = request.systemPrompt && request.systemPrompt.length > 0 ? request.systemPrompt : systemMessages.length > 0 ? systemMessages.map((m) => m.content).join("\n") : void 0;
1988
2188
  const response = await this.withRetry(async () => {
1989
2189
  return await this.fetchWithTimeout(
1990
2190
  `${this.config.baseUrl}/models/${model}:generateContent`,
@@ -1992,24 +2192,30 @@ var GeminiProvider = class extends BaseProvider {
1992
2192
  method: "POST",
1993
2193
  headers: this.authHeaders(),
1994
2194
  body: JSON.stringify({
1995
- systemInstruction: request.systemPrompt && request.systemPrompt.length > 0 ? { parts: [{ text: request.systemPrompt }] } : void 0,
2195
+ systemInstruction: systemPrompt && systemPrompt.length > 0 ? { parts: [{ text: systemPrompt }] } : void 0,
1996
2196
  contents: toGeminiContents(request.messages),
1997
2197
  generationConfig: {
1998
- temperature: request.temperature || 0.7,
1999
- maxOutputTokens: request.maxTokens || 2048,
2000
- topP: request.topP || 0.9
2198
+ temperature: request.temperature ?? 0.7,
2199
+ maxOutputTokens: request.maxTokens ?? 2048,
2200
+ topP: request.topP ?? 0.9
2001
2201
  }
2002
2202
  })
2003
2203
  }
2004
2204
  );
2005
2205
  });
2006
2206
  if (!response.ok) {
2007
- throw new Error(`Gemini API error: ${response.statusText}`);
2207
+ const message = await extractErrorMessageFromResponse(response);
2208
+ throw new AIError(
2209
+ classifyHttpStatus(response.status),
2210
+ `Gemini API error (${response.status}): ${message}`,
2211
+ void 0,
2212
+ { status: response.status, model }
2213
+ );
2008
2214
  }
2009
2215
  const data = await response.json();
2010
- const text = data.candidates[0]?.content?.parts?.map((part) => part.text ?? "").join("") ?? "";
2216
+ const text = data.candidates?.[0]?.content?.parts?.map((part) => part.text ?? "").join("") ?? "";
2011
2217
  if (text.length === 0) {
2012
- throw new AIError2("SERVER_ERROR" /* SERVER_ERROR */, "No content generated from Gemini API", void 0, { model });
2218
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No content generated from Gemini API", void 0, { model });
2013
2219
  }
2014
2220
  return {
2015
2221
  text,
@@ -2020,11 +2226,11 @@ var GeminiProvider = class extends BaseProvider {
2020
2226
  } : void 0,
2021
2227
  model,
2022
2228
  providerId: this.id,
2023
- finishReason: mapGeminiFinishReason(data.candidates[0]?.finishReason)
2229
+ finishReason: mapGeminiFinishReason(data.candidates?.[0]?.finishReason)
2024
2230
  };
2025
2231
  } catch (error) {
2026
- if (error instanceof AIError2) throw error;
2027
- throw new AIError2(
2232
+ if (error instanceof AIError) throw error;
2233
+ throw new AIError(
2028
2234
  "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
2029
2235
  `Gemini text generation failed: ${error instanceof Error ? error.message : "Unknown error"}`,
2030
2236
  error instanceof Error ? error : void 0
@@ -2035,7 +2241,7 @@ var GeminiProvider = class extends BaseProvider {
2035
2241
  const messages = [...request.messages];
2036
2242
  const lastUserIndex = findLastIndex3(messages, (m) => m.role === "user");
2037
2243
  if (lastUserIndex === -1) {
2038
- throw new AIError2(
2244
+ throw new AIError(
2039
2245
  "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
2040
2246
  "generateStructured requires at least one user message"
2041
2247
  );
@@ -2072,12 +2278,18 @@ var GeminiProvider = class extends BaseProvider {
2072
2278
  );
2073
2279
  });
2074
2280
  if (!response.ok) {
2075
- throw new Error(`Gemini API error: ${response.statusText}`);
2281
+ const message = await extractErrorMessageFromResponse(response);
2282
+ throw new AIError(
2283
+ classifyHttpStatus(response.status),
2284
+ `Gemini image generation error (${response.status}): ${message}`,
2285
+ void 0,
2286
+ { status: response.status }
2287
+ );
2076
2288
  }
2077
2289
  const data = await response.json();
2078
- const imageData = data.candidates[0]?.content?.parts[0]?.inlineData;
2290
+ const imageData = data.candidates?.[0]?.content?.parts?.[0]?.inlineData;
2079
2291
  if (!imageData) {
2080
- throw new Error("No image data in response");
2292
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No image data in Gemini response");
2081
2293
  }
2082
2294
  const url = `data:${imageData.mimeType};base64,${imageData.data}`;
2083
2295
  return {
@@ -2456,6 +2668,41 @@ var FalProvider = class extends BaseProvider {
2456
2668
  }
2457
2669
  };
2458
2670
 
2671
+ // src/providers/pruna/pruna-generate.ts
2672
+ var prunaCorePromise = null;
2673
+ async function loadPrunaCore() {
2674
+ if (!prunaCorePromise) {
2675
+ const specifier = "@umituz/pruna-provider/core";
2676
+ prunaCorePromise = import(specifier);
2677
+ }
2678
+ return prunaCorePromise;
2679
+ }
2680
+ async function* generateWithPruna(request, apiKey, signal, onProgress) {
2681
+ const prunaCore = await loadPrunaCore();
2682
+ if (prunaCore.generateWithPruna) {
2683
+ yield* prunaCore.generateWithPruna(request, apiKey, signal, onProgress);
2684
+ return;
2685
+ }
2686
+ if (!prunaCore.submitPrediction || !prunaCore.pollForResult) {
2687
+ throw new Error("@umituz/pruna-provider/core does not export required functions");
2688
+ }
2689
+ const modelId = request.model || (request.image ? "p-image-edit" : "p-image");
2690
+ const prediction = await prunaCore.submitPrediction(modelId, request, apiKey, signal, onProgress);
2691
+ if (prediction.output && typeof prediction.output === "object" && "url" in prediction.output) {
2692
+ yield {
2693
+ stage: "predicting",
2694
+ result: { url: prediction.output.url, model: modelId }
2695
+ };
2696
+ return;
2697
+ }
2698
+ const pollUrl = prediction.status_url || prediction.generation_url;
2699
+ if (!pollUrl) {
2700
+ throw new Error("Pruna prediction response missing polling URL");
2701
+ }
2702
+ const resultUrl = await prunaCore.pollForResult(pollUrl, apiKey, 60, 2e3, signal, onProgress);
2703
+ yield { stage: "polling", result: { url: resultUrl, model: modelId } };
2704
+ }
2705
+
2459
2706
  // src/providers/pruna/pruna.provider.ts
2460
2707
  var PrunaProvider = class {
2461
2708
  constructor(config) {
@@ -2471,17 +2718,70 @@ var PrunaProvider = class {
2471
2718
  async estimateCost(_request) {
2472
2719
  return 1;
2473
2720
  }
2474
- async generateImage(_request) {
2475
- throw new AIError2("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2721
+ async generateImage(request) {
2722
+ let finalUrl;
2723
+ const model = this.config.models?.textToImage;
2724
+ for await (const stage of generateWithPruna(
2725
+ { prompt: request.prompt, model, quality: request.quality },
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 image generated by Pruna AI");
2733
+ }
2734
+ return {
2735
+ id: Date.now().toString(),
2736
+ type: "image",
2737
+ url: finalUrl,
2738
+ metadata: { provider: this.id, model: model ?? "p-image" },
2739
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2740
+ };
2476
2741
  }
2477
- async generateVideo(_request) {
2478
- throw new AIError2("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2742
+ async generateVideo(request) {
2743
+ let finalUrl;
2744
+ const model = this.config.models?.textToVideo;
2745
+ for await (const stage of generateWithPruna(
2746
+ { prompt: request.prompt, model, duration: request.duration, aspectRatio: request.aspectRatio },
2747
+ this.config.apiKey
2748
+ )) {
2749
+ if (stage.error) throw stage.error;
2750
+ if (stage.result?.url) finalUrl = stage.result.url;
2751
+ }
2752
+ if (!finalUrl) {
2753
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated by Pruna AI");
2754
+ }
2755
+ return {
2756
+ id: Date.now().toString(),
2757
+ type: "video",
2758
+ url: finalUrl,
2759
+ metadata: { provider: this.id, model: model ?? "p-video" },
2760
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2761
+ };
2479
2762
  }
2480
- async imageToVideo(_request) {
2481
- throw new AIError2("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2763
+ async imageToVideo(request) {
2764
+ let finalUrl;
2765
+ for await (const stage of generateWithPruna(
2766
+ { image: request.imageUrl, prompt: request.prompt, duration: request.duration, motion: request.motion },
2767
+ this.config.apiKey
2768
+ )) {
2769
+ if (stage.error) throw stage.error;
2770
+ if (stage.result?.url) finalUrl = stage.result.url;
2771
+ }
2772
+ if (!finalUrl) {
2773
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated from image by Pruna AI");
2774
+ }
2775
+ return {
2776
+ id: Date.now().toString(),
2777
+ type: "video",
2778
+ url: finalUrl,
2779
+ metadata: { provider: this.id, model: "p-image-to-video" },
2780
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2781
+ };
2482
2782
  }
2483
2783
  async videoToVideo(_request) {
2484
- throw new AIError2("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2784
+ throw new AIError("CLIENT_ERROR" /* CLIENT_ERROR */, "Pruna AI provider does not support video-to-video conversion");
2485
2785
  }
2486
2786
  };
2487
2787
 
@@ -3088,11 +3388,12 @@ var buildScriptResult = (parsed, duration) => {
3088
3388
  if (!script) {
3089
3389
  throw new Error("AI response missing script field");
3090
3390
  }
3391
+ const wordCount = script.trim().length === 0 ? 0 : script.trim().split(/\s+/).length;
3091
3392
  return {
3092
3393
  ...parsed,
3093
3394
  script,
3094
3395
  duration,
3095
- wordCount: script.split(" ").length
3396
+ wordCount
3096
3397
  };
3097
3398
  };
3098
3399
  var VideoScriptService = class {
@@ -3287,7 +3588,6 @@ var SeoService = class {
3287
3588
  analyzeKeywords(content, keywords) {
3288
3589
  const validatedContent = validateContentBody(content);
3289
3590
  const validatedKeywords = validateKeywordList(keywords);
3290
- void countWordsInContent(validatedContent);
3291
3591
  return validatedKeywords.map((keyword) => {
3292
3592
  const count = countKeywordOccurrences(validatedContent, keyword);
3293
3593
  const density = calculateKeywordDensity(validatedContent, keyword);
@@ -3382,68 +3682,20 @@ var ABTestService = class {
3382
3682
  });
3383
3683
  const scoreA = predictions[0]?.predictedEngagement ?? FALLBACK_ENGAGEMENT;
3384
3684
  const scoreB = predictions[1]?.predictedEngagement ?? FALLBACK_ENGAGEMENT;
3685
+ const isTie = scoreA === scoreB;
3385
3686
  const winner = selectHigherScoreVariantId(scoreA, scoreB, VARIANT_A_ID, VARIANT_B_ID);
3386
3687
  const improvement = calculateImprovementPercentage(scoreA, scoreB);
3387
3688
  return {
3388
3689
  winner,
3389
3690
  confidence: AB_TEST_DEFAULT_CONFIDENCE,
3390
- improvement: `+${improvement}% engagement`,
3391
- reasoning: winner === VARIANT_A_ID ? "Variant A has more engaging content" : "Variant B has more engaging content",
3691
+ improvement: isTie ? "0%" : `+${improvement}% engagement`,
3692
+ reasoning: isTie ? "Both variants have equal predicted engagement" : winner === VARIANT_A_ID ? "Variant A has more engaging content" : "Variant B has more engaging content",
3392
3693
  recommendations: [...DEFAULT_RECOMMENDATIONS]
3393
3694
  };
3394
3695
  }
3395
3696
  };
3396
3697
 
3397
- // src/content/infrastructure/utils/jsonParser.ts
3398
- function repairCommonJsonDefects(text) {
3399
- return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
3400
- }
3401
- function safeJSONParse(text, fallback, onError) {
3402
- try {
3403
- return JSON.parse(text);
3404
- } catch (error) {
3405
- try {
3406
- return JSON.parse(repairCommonJsonDefects(text));
3407
- } catch {
3408
- }
3409
- if (error instanceof SyntaxError) {
3410
- const positionMatch = error.message.match(/position (\d+)/);
3411
- const position = positionMatch ? parseInt(positionMatch[1], 10) : -1;
3412
- if (position > 0) {
3413
- text.substring(Math.max(0, position - 50), position + 50);
3414
- }
3415
- }
3416
- return fallback;
3417
- }
3418
- }
3419
- function extractJSON(text) {
3420
- const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
3421
- if (codeBlockMatch && codeBlockMatch[1]) {
3422
- const candidate = codeBlockMatch[1].trim();
3423
- if (candidate.startsWith("{") || candidate.startsWith("[")) {
3424
- return candidate;
3425
- }
3426
- }
3427
- const objectMatch = text.match(/\{[\s\S]*\}/);
3428
- if (objectMatch) {
3429
- return objectMatch[0];
3430
- }
3431
- const arrayMatch = text.match(/\[[\s\S]*\]/);
3432
- if (arrayMatch) {
3433
- return arrayMatch[0];
3434
- }
3435
- return null;
3436
- }
3437
- function parseAIResponse(response, fallback, onError) {
3438
- const json = extractJSON(response);
3439
- if (!json) {
3440
- return fallback;
3441
- }
3442
- return safeJSONParse(json, fallback);
3443
- }
3444
-
3445
3698
  // src/content/application/services/ImagePromptService.ts
3446
- var JSON_ARRAY_PATTERN = /\[[\s\S]*\]/;
3447
3699
  var ImagePromptService = class {
3448
3700
  constructor(executor) {
3449
3701
  this.executor = executor;
@@ -3457,10 +3709,11 @@ var ImagePromptService = class {
3457
3709
  async generateImagePromptsForBlog(blogContent) {
3458
3710
  const truncated = truncateForBlogPromptPreview(validateContentBody(blogContent));
3459
3711
  const prompt = PromptBuilder.blogImagePrompts(truncated);
3460
- const response = await this.executor.runText(prompt, { maxTokens: 500, temperature: 0.8 });
3461
- const jsonMatch = response.match(JSON_ARRAY_PATTERN);
3462
- if (!jsonMatch) return [];
3463
- return safeJSONParse(jsonMatch[0], []);
3712
+ const prompts = await this.executor.runJson(prompt, [], {
3713
+ maxTokens: 500,
3714
+ temperature: 0.8
3715
+ });
3716
+ return Array.isArray(prompts) ? prompts : [];
3464
3717
  }
3465
3718
  };
3466
3719
 
@@ -3504,11 +3757,16 @@ var ContentCalendarService = class {
3504
3757
  const validatedNiche = validateTopicText(niche);
3505
3758
  const validatedDays = validateCalendarDays(days);
3506
3759
  const prompt = PromptBuilder.contentCalendar(validatedNiche, validatedDays);
3507
- const data = await this.executor.runJson(prompt, [], {
3508
- maxTokens: estimateTokenBudgetForCalendarDays(validatedDays),
3509
- temperature: 0.8
3510
- });
3511
- return data.map(toCalendarEntry);
3760
+ const data = await this.executor.runJson(
3761
+ prompt,
3762
+ [],
3763
+ {
3764
+ maxTokens: estimateTokenBudgetForCalendarDays(validatedDays),
3765
+ temperature: 0.8
3766
+ }
3767
+ );
3768
+ const items = Array.isArray(data) ? data : Array.isArray(data?.calendar) ? data.calendar : Array.isArray(data?.entries) ? data.entries : [];
3769
+ return items.map(toCalendarEntry);
3512
3770
  }
3513
3771
  };
3514
3772
 
@@ -3556,6 +3814,54 @@ var MediaGenerationService = class {
3556
3814
  }
3557
3815
  };
3558
3816
 
3817
+ // src/content/infrastructure/utils/jsonParser.ts
3818
+ function repairCommonJsonDefects2(text) {
3819
+ return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
3820
+ }
3821
+ function safeJSONParse(text, fallback, onError) {
3822
+ try {
3823
+ return JSON.parse(text);
3824
+ } catch (error) {
3825
+ try {
3826
+ return JSON.parse(repairCommonJsonDefects2(text));
3827
+ } catch {
3828
+ }
3829
+ if (error instanceof SyntaxError) {
3830
+ const positionMatch = error.message.match(/position (\d+)/);
3831
+ const position = positionMatch ? parseInt(positionMatch[1], 10) : -1;
3832
+ if (position > 0) {
3833
+ text.substring(Math.max(0, position - 50), position + 50);
3834
+ }
3835
+ }
3836
+ return fallback;
3837
+ }
3838
+ }
3839
+ function extractJSON(text) {
3840
+ const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
3841
+ if (codeBlockMatch && codeBlockMatch[1]) {
3842
+ const candidate = codeBlockMatch[1].trim();
3843
+ if (candidate.startsWith("{") || candidate.startsWith("[")) {
3844
+ return candidate;
3845
+ }
3846
+ }
3847
+ const objectMatch = text.match(/\{[\s\S]*\}/);
3848
+ if (objectMatch) {
3849
+ return objectMatch[0];
3850
+ }
3851
+ const arrayMatch = text.match(/\[[\s\S]*\]/);
3852
+ if (arrayMatch) {
3853
+ return arrayMatch[0];
3854
+ }
3855
+ return null;
3856
+ }
3857
+ function parseAIResponse(response, fallback, onError) {
3858
+ const json = extractJSON(response);
3859
+ if (!json) {
3860
+ return fallback;
3861
+ }
3862
+ return safeJSONParse(json, fallback);
3863
+ }
3864
+
3559
3865
  // src/content/application/services/GenerationExecutor.ts
3560
3866
  var GenerationExecutor = class {
3561
3867
  constructor(generator) {
@@ -3586,6 +3892,13 @@ var isTextGenerator = (value) => typeof value === "object" && value !== null &&
3586
3892
  function toAIProviderConfig(config) {
3587
3893
  const legacy = config;
3588
3894
  const modern = config;
3895
+ const anthropic = modern.anthropic ?? (legacy.anthropic?.enabled ? {
3896
+ apiKey: legacy.anthropic.apiKey,
3897
+ baseUrl: legacy.anthropic.baseUrl,
3898
+ timeoutMs: legacy.anthropic.timeout,
3899
+ models: legacy.anthropic.models,
3900
+ effort: legacy.anthropic.effort
3901
+ } : void 0);
3589
3902
  const groq = modern.groq ?? (legacy.groq?.enabled ? {
3590
3903
  apiKey: legacy.groq.apiKey,
3591
3904
  baseUrl: legacy.groq.baseUrl,
@@ -3600,7 +3913,7 @@ function toAIProviderConfig(config) {
3600
3913
  priority: modern.priority,
3601
3914
  fallbackEnabled: modern.fallbackEnabled,
3602
3915
  defaultTier: modern.defaultTier,
3603
- anthropic: modern.anthropic,
3916
+ ...anthropic ? { anthropic } : {},
3604
3917
  ...groq ? { groq } : {},
3605
3918
  ...gemini ? { gemini } : {},
3606
3919
  ...fal ? { fal } : {},