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