@umituz/web-ai 1.0.2 → 1.0.4

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 (47) hide show
  1. package/dist/{AIErrors-B2ogd1PB.d.ts → AIErrors-CLjXRty-.d.ts} +99 -92
  2. package/dist/{AIErrors-C5EasV_c.d.cts → AIErrors-DjZXUiLT.d.cts} +99 -92
  3. package/dist/PromptBuilder-B5Lm1znm.d.ts +215 -0
  4. package/dist/PromptBuilder-DIUZlfV6.d.cts +215 -0
  5. package/dist/{text-generation.service-BQO3Tvz2.d.cts → cancellation-error-Cj7fJyCg.d.cts} +66 -3
  6. package/dist/{text-generation.service-CcYQM6Ch.d.ts → cancellation-error-CkMn5Kft.d.ts} +66 -3
  7. package/dist/chat/index.cjs.map +1 -1
  8. package/dist/chat/index.d.cts +3 -3
  9. package/dist/chat/index.d.ts +3 -3
  10. package/dist/chat/index.js.map +1 -1
  11. package/dist/{chat.service-CL1qtYxe.d.cts → chat.service-9M-KpLo-.d.cts} +1 -1
  12. package/dist/{chat.service-q05GnhEm.d.ts → chat.service-B4FLFxR1.d.ts} +1 -1
  13. package/dist/content/index.cjs +1582 -452
  14. package/dist/content/index.cjs.map +1 -1
  15. package/dist/content/index.d.cts +165 -219
  16. package/dist/content/index.d.ts +165 -219
  17. package/dist/content/index.js +1525 -453
  18. package/dist/content/index.js.map +1 -1
  19. package/dist/{create-ai-9PW752z8.d.ts → create-ai-0eZFFqTw.d.ts} +1 -1
  20. package/dist/{create-ai-CXYKPK6G.d.cts → create-ai-BrKR_5fQ.d.cts} +1 -1
  21. package/dist/hooks/index.cjs +924 -31
  22. package/dist/hooks/index.cjs.map +1 -1
  23. package/dist/hooks/index.d.cts +44 -8
  24. package/dist/hooks/index.d.ts +44 -8
  25. package/dist/hooks/index.js +919 -32
  26. package/dist/hooks/index.js.map +1 -1
  27. package/dist/index-BUVULo3h.d.ts +177 -0
  28. package/dist/index-BulSkB2L.d.cts +177 -0
  29. package/dist/index.cjs +3057 -563
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +8 -5
  32. package/dist/index.d.ts +8 -5
  33. package/dist/index.js +3011 -564
  34. package/dist/index.js.map +1 -1
  35. package/dist/{provider.factory-DfDfk0dK.d.ts → provider.factory-BwPnqSEU.d.ts} +3 -1
  36. package/dist/{provider.factory-BxsfK4WW.d.cts → provider.factory-DyfJtR8M.d.cts} +3 -1
  37. package/dist/providers/index.cjs +818 -27
  38. package/dist/providers/index.cjs.map +1 -1
  39. package/dist/providers/index.d.cts +50 -8
  40. package/dist/providers/index.d.ts +50 -8
  41. package/dist/providers/index.js +806 -28
  42. package/dist/providers/index.js.map +1 -1
  43. package/dist/wizard/index.d.cts +2 -86
  44. package/dist/wizard/index.d.ts +2 -86
  45. package/package.json +21 -1
  46. package/dist/ContentGeneration-B6iK2B33.d.cts +0 -93
  47. package/dist/ContentGeneration-B6iK2B33.d.ts +0 -93
package/dist/index.js CHANGED
@@ -1082,6 +1082,26 @@ function isMediaProvider(provider) {
1082
1082
  return provider.capabilities.includes("image") || provider.capabilities.includes("video");
1083
1083
  }
1084
1084
 
1085
+ // src/content/domain/errors/AIErrors.ts
1086
+ var ValidationError = class extends AIError {
1087
+ constructor(message) {
1088
+ super(message, "VALIDATION_ERROR");
1089
+ this.name = "ValidationError";
1090
+ }
1091
+ };
1092
+ var ProviderUnavailableError = class extends AIError {
1093
+ constructor(providerId) {
1094
+ super(`Provider ${providerId} is not available`, "PROVIDER_UNAVAILABLE");
1095
+ this.name = "ProviderUnavailableError";
1096
+ }
1097
+ };
1098
+ var ProviderQuotaExhaustedError = class extends AIError {
1099
+ constructor(providerId) {
1100
+ super(`Provider ${providerId} quota exhausted`, "PROVIDER_QUOTA_EXHAUSTED");
1101
+ this.name = "ProviderQuotaExhaustedError";
1102
+ }
1103
+ };
1104
+
1085
1105
  // src/content/domain/limits/ProviderTimingConfig.ts
1086
1106
  var ProviderTimingConfig = {
1087
1107
  DEFAULT_TIMEOUT_MS: 3e4,
@@ -1424,6 +1444,9 @@ var ProviderFactory = class {
1424
1444
  );
1425
1445
  }
1426
1446
  };
1447
+ function createProviderFactory(config) {
1448
+ return new ProviderFactory(config);
1449
+ }
1427
1450
 
1428
1451
  // src/providers/anthropic/anthropic.constants.ts
1429
1452
  var ANTHROPIC_MODELS = {
@@ -2453,6 +2476,9 @@ function toGroqMessages(messages, systemPrompt) {
2453
2476
  }
2454
2477
  return wire;
2455
2478
  }
2479
+ function createGroqProvider(config) {
2480
+ return new GroqProvider(config);
2481
+ }
2456
2482
  function findLastIndex2(items, predicate) {
2457
2483
  for (let i = items.length - 1; i >= 0; i--) {
2458
2484
  if (predicate(items[i])) return i;
@@ -2719,6 +2745,128 @@ function createGeminiProvider(config) {
2719
2745
  return new GeminiProvider(config);
2720
2746
  }
2721
2747
 
2748
+ // src/content/domain/predicates/KeywordProminenceClassifier.ts
2749
+ var HIGH_DENSITY_THRESHOLD = 2;
2750
+ var MEDIUM_DENSITY_THRESHOLD = 1;
2751
+ function classifyKeywordProminence(densityPercent) {
2752
+ if (densityPercent > HIGH_DENSITY_THRESHOLD) return "high";
2753
+ if (densityPercent > MEDIUM_DENSITY_THRESHOLD) return "medium";
2754
+ return "low";
2755
+ }
2756
+ function isUnderutilizedKeyword(densityPercent) {
2757
+ return densityPercent < 1;
2758
+ }
2759
+
2760
+ // src/content/domain/types/index.ts
2761
+ var PLATFORM_SPECS = {
2762
+ twitter: {
2763
+ maxLength: 280,
2764
+ style: "concise, engaging, thread-friendly",
2765
+ emojiSupport: true,
2766
+ hashtagSupport: true,
2767
+ imageSupport: true,
2768
+ videoSupport: true,
2769
+ maxHashtags: 5
2770
+ },
2771
+ linkedin: {
2772
+ maxLength: 3e3,
2773
+ style: "professional, insightful, industry-focused",
2774
+ emojiSupport: true,
2775
+ hashtagSupport: true,
2776
+ imageSupport: true,
2777
+ videoSupport: true,
2778
+ maxHashtags: 5
2779
+ },
2780
+ instagram: {
2781
+ maxLength: 2200,
2782
+ style: "visual, engaging, emoji-rich",
2783
+ emojiSupport: true,
2784
+ hashtagSupport: true,
2785
+ imageSupport: true,
2786
+ videoSupport: true,
2787
+ maxHashtags: 30
2788
+ },
2789
+ threads: {
2790
+ maxLength: 500,
2791
+ style: "conversational, community-driven",
2792
+ emojiSupport: true,
2793
+ hashtagSupport: true,
2794
+ imageSupport: true,
2795
+ videoSupport: true,
2796
+ maxHashtags: 5
2797
+ },
2798
+ tiktok: {
2799
+ maxLength: 150,
2800
+ style: "catchy, trending, short-form",
2801
+ emojiSupport: true,
2802
+ hashtagSupport: true,
2803
+ imageSupport: false,
2804
+ videoSupport: true,
2805
+ maxHashtags: 5
2806
+ },
2807
+ facebook: {
2808
+ maxLength: 63206,
2809
+ style: "conversational, engaging, shareable",
2810
+ emojiSupport: true,
2811
+ hashtagSupport: true,
2812
+ imageSupport: true,
2813
+ videoSupport: true,
2814
+ maxHashtags: 5
2815
+ }
2816
+ };
2817
+
2818
+ // src/content/domain/predicates/HashtagPriorityRanker.ts
2819
+ var DEFAULT_HASHTAG_BUDGET = Number.MAX_SAFE_INTEGER;
2820
+ function resolveHashtagBudget(platform) {
2821
+ return PLATFORM_SPECS[platform]?.maxHashtags ?? DEFAULT_HASHTAG_BUDGET;
2822
+ }
2823
+ function rankAndTrimHashtags(hashtags, budget) {
2824
+ if (hashtags.length <= budget) return [...hashtags];
2825
+ return [...hashtags].sort((a, b) => b.length - a.length).slice(0, budget);
2826
+ }
2827
+
2828
+ // src/content/domain/predicates/QualityRecommenderByRatio.ts
2829
+ var WIDE_RATIOS = /* @__PURE__ */ new Set(["16:9", "4:3"]);
2830
+ function recommendQualityForAspectRatio(ratio) {
2831
+ return WIDE_RATIOS.has(ratio) ? "hd" : "standard";
2832
+ }
2833
+
2834
+ // src/content/domain/predicates/PlatformSpecResolver.ts
2835
+ var FALLBACK_SPEC = {
2836
+ maxLength: 280,
2837
+ style: "concise, engaging, audience-friendly",
2838
+ emojiSupport: true,
2839
+ hashtagSupport: true,
2840
+ imageSupport: true,
2841
+ videoSupport: true,
2842
+ maxHashtags: 5
2843
+ };
2844
+ function resolvePlatformSpec(platform) {
2845
+ return PLATFORM_SPECS[platform] ?? FALLBACK_SPEC;
2846
+ }
2847
+ function resolveMaxContentLength(platform, override) {
2848
+ return override ?? resolvePlatformSpec(platform).maxLength;
2849
+ }
2850
+
2851
+ // src/content/domain/predicates/ScoreRangeGuard.ts
2852
+ var SCORE_MIN = 0;
2853
+ var SCORE_MAX = 100;
2854
+ var SCORE_FALLBACK = 50;
2855
+ var CONFIDENCE_MIN = 0;
2856
+ var CONFIDENCE_MAX = 1;
2857
+ var CONFIDENCE_FALLBACK = 0.5;
2858
+ function clampScoreToValidRange(score, fallback = SCORE_FALLBACK) {
2859
+ if (!Number.isFinite(score)) return fallback;
2860
+ return Math.max(SCORE_MIN, Math.min(SCORE_MAX, score));
2861
+ }
2862
+ function clampConfidenceToValidRange(confidence, fallback = CONFIDENCE_FALLBACK) {
2863
+ if (!Number.isFinite(confidence)) return fallback;
2864
+ return Math.max(CONFIDENCE_MIN, Math.min(CONFIDENCE_MAX, confidence));
2865
+ }
2866
+
2867
+ // src/content/domain/predicates/AbTestConfidence.ts
2868
+ var AB_TEST_DEFAULT_CONFIDENCE = 0.7;
2869
+
2722
2870
  // src/content/domain/predicates/FalImageRenderingOptions.ts
2723
2871
  var FAL_FORMAT_TO_IMAGE_SIZE = {
2724
2872
  square: "square_hd",
@@ -2915,674 +3063,2973 @@ function createFalProvider(config) {
2915
3063
  return new FalProvider(config);
2916
3064
  }
2917
3065
 
2918
- // src/providers/pruna/pruna-generate.ts
2919
- var prunaCorePromise = null;
2920
- async function loadPrunaCore() {
2921
- if (!prunaCorePromise) {
2922
- const specifier = "@umituz/pruna-provider/core";
2923
- prunaCorePromise = import(specifier);
3066
+ // src/providers/pruna/client/core/constants/limits.ts
3067
+ var DEFAULT_ASPECT_RATIO = "16:9";
3068
+ var P_VIDEO_DEFAULTS = {
3069
+ duration: 5,
3070
+ resolution: "720p",
3071
+ fps: 24,
3072
+ draft: false,
3073
+ promptUpsampling: true
3074
+ };
3075
+ var POLL_DEFAULTS = {
3076
+ intervalMs: 3e3,
3077
+ maxAttempts: 120,
3078
+ maxIntervalMs: 1e4,
3079
+ backoffBase: 1.5,
3080
+ jitterPercent: 0.1
3081
+ };
3082
+ var DEFAULT_DEDUPLICATOR_TTL_MS = 3e4;
3083
+ var DEDUPLICATOR_CLEANUP_GRACE_MS = 100;
3084
+
3085
+ // src/providers/pruna/client/core/constants/network.ts
3086
+ var HTTPS_PREFIX = "https://";
3087
+ var HTTP_PREFIX = "http://";
3088
+ var BASE64_MARKER = "base64,";
3089
+ var PRUNA_BASE_URL = "https://api.pruna.ai";
3090
+ var PRUNA_PREDICTIONS_URL = `${PRUNA_BASE_URL}/v1/predictions`;
3091
+ var PRUNA_FILES_URL = `${PRUNA_BASE_URL}/v1/files`;
3092
+ var AUTH_FAILURE_STATUSES = [401, 403];
3093
+ var MIN_API_KEY_LENGTH = 1;
3094
+ var MAX_API_KEY_LENGTH = 256;
3095
+
3096
+ // src/providers/pruna/client/core/constants/mime-types.ts
3097
+ var MIME_PNG = "image/png";
3098
+ var MIME_JPEG = "image/jpeg";
3099
+ var MIME_WEBP = "image/webp";
3100
+ var DEFAULT_IMAGE_MIME = MIME_PNG;
3101
+ var MIME_FILE_EXTENSION = {
3102
+ [MIME_PNG]: "png",
3103
+ [MIME_JPEG]: "jpg",
3104
+ [MIME_WEBP]: "webp"
3105
+ };
3106
+ var IMAGE_SIGNATURES = {
3107
+ PNG: [137, 80],
3108
+ JPEG: [255, 216],
3109
+ WEBP_RIFF: [82, 73]
3110
+ };
3111
+
3112
+ // src/providers/pruna/client/core/errors/cancellation-error.ts
3113
+ var CANCELLATION_MESSAGE = "Request cancelled by user";
3114
+ var CancellationError = class extends Error {
3115
+ constructor(message = CANCELLATION_MESSAGE) {
3116
+ super(message);
3117
+ this.name = "CancellationError";
3118
+ }
3119
+ };
3120
+
3121
+ // src/providers/pruna/client/core/errors/authentication-error.ts
3122
+ var AUTHENTICATION_MESSAGE = "Authentication failed. Please check your API key.";
3123
+ var AuthenticationError = class extends Error {
3124
+ constructor(message = AUTHENTICATION_MESSAGE) {
3125
+ super(message);
3126
+ this.name = "AuthenticationError";
3127
+ }
3128
+ };
3129
+
3130
+ // src/providers/pruna/client/core/errors/timeout-error.ts
3131
+ var TIMEOUT_MESSAGE = "Generation timed out. Maximum polling attempts reached.";
3132
+ var TimeoutError = class extends Error {
3133
+ constructor(message = TIMEOUT_MESSAGE) {
3134
+ super(message);
3135
+ this.name = "TimeoutError";
3136
+ }
3137
+ };
3138
+
3139
+ // src/providers/pruna/client/core/errors/validation-error.ts
3140
+ var ValidationError2 = class extends Error {
3141
+ field;
3142
+ reason;
3143
+ receivedValue;
3144
+ constructor(context) {
3145
+ super(`Invalid ${context.field}: ${context.reason}`);
3146
+ this.name = "ValidationError";
3147
+ this.field = context.field;
3148
+ this.reason = context.reason;
3149
+ this.receivedValue = context.receivedValue;
3150
+ }
3151
+ };
3152
+
3153
+ // src/providers/pruna/client/core/utils/base64.ts
3154
+ var globalScope = globalThis;
3155
+ var hasBuffer = typeof globalScope.Buffer?.from === "function";
3156
+ function base64ToBytes(raw) {
3157
+ if (hasBuffer) {
3158
+ const bufferFactory = globalScope.Buffer;
3159
+ if (!bufferFactory) {
3160
+ throw new Error("Buffer unavailable despite detection flag");
3161
+ }
3162
+ const buf = bufferFactory.from(raw, "base64");
3163
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
2924
3164
  }
2925
- return prunaCorePromise;
3165
+ const decoded = atob(raw);
3166
+ return Uint8Array.from(decoded, (char) => char.charCodeAt(0));
2926
3167
  }
2927
- async function* generateWithPruna(request, apiKey, signal, onProgress) {
2928
- const prunaCore = await loadPrunaCore();
2929
- if (prunaCore.generateWithPruna) {
2930
- yield* prunaCore.generateWithPruna(request, apiKey, signal, onProgress);
2931
- return;
2932
- }
2933
- if (!prunaCore.submitPrediction || !prunaCore.pollForResult) {
2934
- throw new Error("@umituz/pruna-provider/core does not export required functions");
2935
- }
2936
- const modelId = request.model || (request.image ? "p-image-edit" : "p-image");
2937
- const prediction = await prunaCore.submitPrediction(modelId, request, apiKey, signal, onProgress);
2938
- if (prediction.output && typeof prediction.output === "object" && "url" in prediction.output) {
2939
- yield {
2940
- stage: "predicting",
2941
- result: { url: prediction.output.url, model: modelId }
2942
- };
2943
- return;
3168
+
3169
+ // src/providers/pruna/client/core/utils/mime-detector.ts
3170
+ var MIN_SIGNATURE_BYTES = 2;
3171
+ function matchesSignature(bytes, signature) {
3172
+ if (bytes.length < signature.length) return false;
3173
+ return signature.every((value, index) => bytes[index] === value);
3174
+ }
3175
+ function detectImageMime(bytes) {
3176
+ if (bytes.length < MIN_SIGNATURE_BYTES) return DEFAULT_IMAGE_MIME;
3177
+ if (matchesSignature(bytes, IMAGE_SIGNATURES.PNG)) return MIME_PNG;
3178
+ if (matchesSignature(bytes, IMAGE_SIGNATURES.JPEG)) return MIME_JPEG;
3179
+ if (matchesSignature(bytes, IMAGE_SIGNATURES.WEBP_RIFF)) return MIME_WEBP;
3180
+ return DEFAULT_IMAGE_MIME;
3181
+ }
3182
+
3183
+ // src/providers/pruna/client/core/utils/api-error-parser.ts
3184
+ async function parseApiError(response, fallback) {
3185
+ const payload = await response.json().catch(() => ({ message: response.statusText }));
3186
+ const message = payload.message ?? `${fallback}: ${response.status}`;
3187
+ const error = new Error(message);
3188
+ error.statusCode = response.status;
3189
+ return error;
3190
+ }
3191
+
3192
+ // src/providers/pruna/client/core/protocol/http-status.ts
3193
+ function isAuthFailureStatus(status) {
3194
+ return AUTH_FAILURE_STATUSES.includes(status);
3195
+ }
3196
+
3197
+ // src/providers/pruna/client/core/protocol/job-status.ts
3198
+ var TERMINAL_SUCCESS_STATUSES = ["succeeded", "completed"];
3199
+ var TERMINAL_FAILURE_STATUSES = ["failed"];
3200
+ function isTerminalSuccessStatus(status) {
3201
+ if (!status) return false;
3202
+ return TERMINAL_SUCCESS_STATUSES.includes(status);
3203
+ }
3204
+ function isTerminalFailureStatus(status) {
3205
+ if (!status) return false;
3206
+ return TERMINAL_FAILURE_STATUSES.includes(status);
3207
+ }
3208
+
3209
+ // src/providers/pruna/client/core/protocol/abort-signal-classifier.ts
3210
+ function isAbortError(err) {
3211
+ if (err instanceof DOMException && err.code === DOMException.ABORT_ERR) return true;
3212
+ if (err instanceof Error && err.name === "AbortError") return true;
3213
+ return false;
3214
+ }
3215
+
3216
+ // src/providers/pruna/client/core/protocol/pruna-url-builder.ts
3217
+ function isAbsoluteHttpUrl(value) {
3218
+ return value.startsWith(HTTPS_PREFIX) || value.startsWith(HTTP_PREFIX);
3219
+ }
3220
+ function isHttpsUrl(value) {
3221
+ return value.startsWith(HTTPS_PREFIX);
3222
+ }
3223
+ function buildPrunaApiUrl(pathOrUrl) {
3224
+ if (isAbsoluteHttpUrl(pathOrUrl)) return pathOrUrl;
3225
+ return `${PRUNA_BASE_URL}${pathOrUrl}`;
3226
+ }
3227
+ function buildFileUrlById(fileId) {
3228
+ return `${PRUNA_FILES_URL}/${fileId}`;
3229
+ }
3230
+
3231
+ // src/providers/pruna/client/core/math/exponential-backoff.ts
3232
+ function calculateExponentialBackoff2(inputs) {
3233
+ const {
3234
+ attempt,
3235
+ baseIntervalMs,
3236
+ maxIntervalMs = POLL_DEFAULTS.maxIntervalMs,
3237
+ jitterPercent = POLL_DEFAULTS.jitterPercent,
3238
+ growthBase = POLL_DEFAULTS.backoffBase
3239
+ } = inputs;
3240
+ const safeAttempt = Math.max(0, attempt);
3241
+ const safeBase = Math.max(0, baseIntervalMs);
3242
+ const exponentialDelay = Math.min(safeBase * Math.pow(growthBase, safeAttempt), maxIntervalMs);
3243
+ const jitter = exponentialDelay * jitterPercent * Math.random();
3244
+ return exponentialDelay + jitter;
3245
+ }
3246
+
3247
+ // src/providers/pruna/client/core/utils/uri-resolver.ts
3248
+ var uriCache = /* @__PURE__ */ new WeakMap();
3249
+ function stripBase64Prefix(image) {
3250
+ if (isHttpsUrl(image)) return image;
3251
+ const base64Index = image.indexOf(BASE64_MARKER);
3252
+ return base64Index !== -1 ? image.slice(base64Index + BASE64_MARKER.length) : image;
3253
+ }
3254
+ function readOutput(output) {
3255
+ if (typeof output === "string") return output;
3256
+ if (Array.isArray(output) && output.length > 0) return output[0] ?? null;
3257
+ if (output !== null && typeof output === "object" && "url" in output) {
3258
+ return output.url;
2944
3259
  }
2945
- const pollUrl = prediction.status_url || prediction.generation_url;
2946
- if (!pollUrl) {
2947
- throw new Error("Pruna prediction response missing polling URL");
3260
+ return null;
3261
+ }
3262
+ function extractUri(data) {
3263
+ const cached = uriCache.get(data);
3264
+ if (cached !== void 0) return cached;
3265
+ if (!isTerminalSuccessStatus(data.status)) {
3266
+ uriCache.set(data, null);
3267
+ return null;
2948
3268
  }
2949
- const resultUrl = await prunaCore.pollForResult(pollUrl, apiKey, 60, 2e3, signal, onProgress);
2950
- yield { stage: "polling", result: { url: resultUrl, model: modelId } };
3269
+ const result = data.generation_url ?? readOutput(data.output) ?? data.video_url ?? data.data ?? null;
3270
+ uriCache.set(data, result);
3271
+ return result;
3272
+ }
3273
+ function resolveUri(uri) {
3274
+ return uri.startsWith("/") ? `${PRUNA_BASE_URL}${uri}` : uri;
2951
3275
  }
2952
3276
 
2953
- // src/providers/pruna/pruna.provider.ts
2954
- var PrunaProvider = class {
2955
- constructor(config) {
2956
- this.config = config;
3277
+ // src/providers/pruna/client/core/services/pruna-client.service.ts
3278
+ function buildFileExtension(mime) {
3279
+ return MIME_FILE_EXTENSION[mime] ?? "bin";
3280
+ }
3281
+ async function uploadImage(base64OrUrl, apiKey, onProgress) {
3282
+ if (isHttpsUrl(base64OrUrl)) return base64OrUrl;
3283
+ if (base64OrUrl.startsWith(HTTP_PREFIX)) {
3284
+ throw new Error("Only HTTPS URLs are supported for security.");
3285
+ }
3286
+ onProgress?.("uploading");
3287
+ const raw = stripBase64Prefix(base64OrUrl);
3288
+ let bytes;
3289
+ try {
3290
+ bytes = base64ToBytes(raw);
3291
+ } catch {
3292
+ throw new Error("Invalid image format. Provide base64 or a valid HTTPS URL.");
2957
3293
  }
2958
- config;
2959
- id = "pruna";
2960
- name = "Pruna AI";
2961
- capabilities = ["image", "video"];
2962
- async healthCheck() {
2963
- return this.config.apiKey ? "healthy" : "unavailable";
3294
+ const mime = detectImageMime(bytes);
3295
+ const buffer = bytes.buffer.slice(
3296
+ bytes.byteOffset,
3297
+ bytes.byteOffset + bytes.byteLength
3298
+ );
3299
+ const blob = new Blob([buffer], { type: mime });
3300
+ const formData = new FormData();
3301
+ formData.append("content", blob, `upload.${buildFileExtension(mime)}`);
3302
+ const res = await fetch(PRUNA_FILES_URL, {
3303
+ method: "POST",
3304
+ headers: { apikey: apiKey },
3305
+ body: formData
3306
+ });
3307
+ if (!res.ok) {
3308
+ throw await parseApiError(res, "File upload error");
2964
3309
  }
2965
- async estimateCost(_request) {
2966
- return 1;
3310
+ const data = await res.json();
3311
+ return data.urls?.get ?? buildFileUrlById(data.id ?? "");
3312
+ }
3313
+ async function submitPrediction(model, input, apiKey, signal, onProgress) {
3314
+ onProgress?.("predicting");
3315
+ const res = await fetch(PRUNA_PREDICTIONS_URL, {
3316
+ method: "POST",
3317
+ headers: {
3318
+ apikey: apiKey,
3319
+ Model: model,
3320
+ "Try-Sync": "true",
3321
+ "Content-Type": "application/json"
3322
+ },
3323
+ body: JSON.stringify({ input }),
3324
+ signal
3325
+ });
3326
+ if (!res.ok) {
3327
+ throw await parseApiError(res, "API error");
2967
3328
  }
2968
- async generateImage(request) {
2969
- let finalUrl;
2970
- const model = this.config.models?.textToImage;
2971
- for await (const stage of generateWithPruna(
2972
- { prompt: request.prompt, model, quality: request.quality },
2973
- this.config.apiKey
2974
- )) {
2975
- if (stage.error) throw stage.error;
2976
- if (stage.result?.url) finalUrl = stage.result.url;
2977
- }
2978
- if (!finalUrl) {
2979
- throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No image generated by Pruna AI");
2980
- }
2981
- return {
2982
- id: Date.now().toString(),
2983
- type: "image",
2984
- url: finalUrl,
2985
- metadata: { provider: this.id, model: model ?? "p-image" },
2986
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
2987
- };
3329
+ return res.json();
3330
+ }
3331
+ var GenerationFailedError = class extends Error {
3332
+ constructor(message) {
3333
+ super(message);
3334
+ this.name = "GenerationFailedError";
2988
3335
  }
2989
- async generateVideo(request) {
2990
- let finalUrl;
2991
- const model = this.config.models?.textToVideo;
2992
- for await (const stage of generateWithPruna(
2993
- { prompt: request.prompt, model, duration: request.duration, aspectRatio: request.aspectRatio },
2994
- this.config.apiKey
2995
- )) {
2996
- if (stage.error) throw stage.error;
2997
- if (stage.result?.url) finalUrl = stage.result.url;
2998
- }
2999
- if (!finalUrl) {
3000
- throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated by Pruna AI");
3001
- }
3002
- return {
3003
- id: Date.now().toString(),
3004
- type: "video",
3005
- url: finalUrl,
3006
- metadata: { provider: this.id, model: model ?? "p-video" },
3007
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
3008
- };
3336
+ };
3337
+ async function attemptPoll(fullUrl, apiKey, signal) {
3338
+ let res;
3339
+ try {
3340
+ res = await fetch(fullUrl, { headers: { apikey: apiKey }, signal });
3341
+ } catch (err) {
3342
+ if (isAbortError(err)) throw new CancellationError();
3343
+ return { outcome: "transient-failure" };
3009
3344
  }
3010
- async imageToVideo(request) {
3011
- let finalUrl;
3012
- for await (const stage of generateWithPruna(
3013
- { image: request.imageUrl, prompt: request.prompt, duration: request.duration, motion: request.motion },
3014
- this.config.apiKey
3015
- )) {
3016
- if (stage.error) throw stage.error;
3017
- if (stage.result?.url) finalUrl = stage.result.url;
3018
- }
3019
- if (!finalUrl) {
3020
- throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated from image by Pruna AI");
3345
+ if (!res.ok) {
3346
+ if (isAuthFailureStatus(res.status)) throw new AuthenticationError();
3347
+ return { outcome: "transient-failure" };
3348
+ }
3349
+ let data;
3350
+ try {
3351
+ data = await res.json();
3352
+ } catch {
3353
+ return { outcome: "transient-failure" };
3354
+ }
3355
+ if (isTerminalSuccessStatus(data.status)) {
3356
+ const uri = extractUri(data);
3357
+ if (uri) return { outcome: "succeeded", uri: resolveUri(uri) };
3358
+ return { outcome: "transient-failure" };
3359
+ }
3360
+ if (isTerminalFailureStatus(data.status)) {
3361
+ throw new GenerationFailedError(data.error ?? "Generation failed during processing.");
3362
+ }
3363
+ return { outcome: "continue" };
3364
+ }
3365
+ async function pollForResult(pollUrl, apiKey, maxAttempts = POLL_DEFAULTS.maxAttempts, intervalMs = POLL_DEFAULTS.intervalMs, signal, onProgress) {
3366
+ const fullUrl = isAbsoluteHttpUrl(pollUrl) ? pollUrl : buildPrunaApiUrl(pollUrl);
3367
+ const controller = new AbortController();
3368
+ const onExternalAbort = () => controller.abort();
3369
+ signal?.addEventListener("abort", onExternalAbort);
3370
+ try {
3371
+ for (let i = 0; i < maxAttempts; i++) {
3372
+ if (signal?.aborted) throw new CancellationError();
3373
+ const delay = calculateExponentialBackoff2({ attempt: i, baseIntervalMs: intervalMs });
3374
+ await new Promise((resolve) => setTimeout(resolve, delay));
3375
+ if (signal?.aborted) throw new CancellationError();
3376
+ onProgress?.("polling", i + 1);
3377
+ const result = await attemptPoll(fullUrl, apiKey, controller.signal);
3378
+ if (result.outcome === "succeeded" && result.uri) {
3379
+ return result.uri;
3380
+ }
3021
3381
  }
3022
- return {
3023
- id: Date.now().toString(),
3024
- type: "video",
3025
- url: finalUrl,
3026
- metadata: { provider: this.id, model: "p-image-to-video" },
3027
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
3028
- };
3382
+ throw new TimeoutError();
3383
+ } finally {
3384
+ signal?.removeEventListener("abort", onExternalAbort);
3385
+ controller.abort();
3029
3386
  }
3030
- async videoToVideo(_request) {
3031
- throw new AIError("CLIENT_ERROR" /* CLIENT_ERROR */, "Pruna AI provider does not support video-to-video conversion");
3387
+ }
3388
+
3389
+ // src/providers/pruna/client/core/math/comparison-hash.ts
3390
+ function sortKeysDeep(value) {
3391
+ if (Array.isArray(value)) {
3392
+ return value.map(sortKeysDeep);
3393
+ }
3394
+ if (value !== null && typeof value === "object") {
3395
+ const record = value;
3396
+ return Object.keys(record).sort().reduce((acc, key) => {
3397
+ acc[key] = sortKeysDeep(record[key]);
3398
+ return acc;
3399
+ }, {});
3032
3400
  }
3033
- };
3034
- function createPrunaProvider(config) {
3035
- return new PrunaProvider(config);
3401
+ return value;
3402
+ }
3403
+ function hashValue(value) {
3404
+ if (value === null || typeof value !== "object") {
3405
+ return String(value);
3406
+ }
3407
+ try {
3408
+ return JSON.stringify(sortKeysDeep(value));
3409
+ } catch {
3410
+ return String(value);
3411
+ }
3412
+ }
3413
+ function compositeHash(...values) {
3414
+ return values.map(hashValue).join("|");
3036
3415
  }
3037
3416
 
3038
- // src/providers/factory/create-ai.ts
3039
- function createAI(config = {}) {
3040
- const factory = new ProviderFactory({
3041
- priority: config.priority,
3042
- fallbackEnabled: config.fallbackEnabled,
3043
- defaultTier: config.defaultTier
3044
- });
3045
- if (config.anthropic?.apiKey) {
3046
- factory.register(new AnthropicProvider(config.anthropic));
3417
+ // src/providers/pruna/client/core/math/cache-key-derivation.ts
3418
+ function deriveGenerationCacheKey(apiKey, input) {
3419
+ return `${apiKey}:${compositeHash(input)}`;
3420
+ }
3421
+
3422
+ // src/providers/pruna/client/core/validation/api-key-validator.ts
3423
+ function assertValidApiKey(apiKey) {
3424
+ if (typeof apiKey !== "string") {
3425
+ throw new ValidationError2({
3426
+ field: "apiKey",
3427
+ reason: "must be a string",
3428
+ receivedValue: typeof apiKey
3429
+ });
3047
3430
  }
3048
- if (config.groq?.apiKey) {
3049
- factory.register(new GroqProvider(config.groq));
3431
+ if (apiKey.length < MIN_API_KEY_LENGTH) {
3432
+ throw new ValidationError2({
3433
+ field: "apiKey",
3434
+ reason: "must not be empty",
3435
+ receivedValue: apiKey
3436
+ });
3050
3437
  }
3051
- if (config.gemini?.apiKey) {
3052
- factory.register(new GeminiProvider({ ...config.gemini, enabled: true }));
3438
+ if (apiKey.length > MAX_API_KEY_LENGTH) {
3439
+ throw new ValidationError2({
3440
+ field: "apiKey",
3441
+ reason: `exceeds maximum length of ${MAX_API_KEY_LENGTH}`,
3442
+ receivedValue: apiKey.length
3443
+ });
3053
3444
  }
3054
- if (config.fal?.apiKey) {
3055
- factory.register(new FalProvider({ ...config.fal, enabled: true }));
3445
+ }
3446
+
3447
+ // src/providers/pruna/client/core/validation/image-source-validator.ts
3448
+ function classifyImageSource(source) {
3449
+ if (source.startsWith(HTTPS_PREFIX)) return "https-url";
3450
+ if (source.startsWith(HTTP_PREFIX)) return "http-url";
3451
+ if (source.includes(BASE64_MARKER) || /^[A-Za-z0-9+/=]+$/.test(source)) return "base64";
3452
+ return "unknown";
3453
+ }
3454
+ function assertSecureImageSource(image, fieldName = "image") {
3455
+ if (typeof image !== "string") {
3456
+ throw new ValidationError2({
3457
+ field: fieldName,
3458
+ reason: "must be a string",
3459
+ receivedValue: typeof image
3460
+ });
3056
3461
  }
3057
- if (config.pruna?.apiKey) {
3058
- factory.register(new PrunaProvider({ ...config.pruna, enabled: true }));
3462
+ const kind = classifyImageSource(image);
3463
+ if (kind === "http-url") {
3464
+ throw new ValidationError2({
3465
+ field: fieldName,
3466
+ reason: "HTTP URLs are not supported; use HTTPS or a base64 data URI",
3467
+ receivedValue: image
3468
+ });
3469
+ }
3470
+ if (kind === "unknown") {
3471
+ throw new ValidationError2({
3472
+ field: fieldName,
3473
+ reason: "must be an HTTPS URL or a base64 data URI",
3474
+ receivedValue: typeof image
3475
+ });
3059
3476
  }
3060
- return factory;
3061
3477
  }
3062
3478
 
3063
- // src/providers/groq/groq-messages.ts
3064
- var createUserMessage = (content) => ({
3065
- role: "user",
3066
- content
3067
- });
3068
- var createAssistantMessage = (content) => ({
3069
- role: "assistant",
3070
- content
3071
- });
3072
- var createSystemMessage = (content) => ({
3073
- role: "system",
3074
- content
3075
- });
3076
- var createMessage = (role, content) => ({
3077
- role,
3078
- content
3079
- });
3080
- var promptToMessages = (prompt) => [
3081
- createUserMessage(prompt)
3479
+ // src/providers/pruna/client/core/validation/aspect-ratio-validator.ts
3480
+ var SUPPORTED_ASPECT_RATIOS = [
3481
+ "16:9",
3482
+ "9:16",
3483
+ "1:1",
3484
+ "4:3",
3485
+ "3:4",
3486
+ "3:2",
3487
+ "2:3"
3082
3488
  ];
3083
- var extractTextFromMessages = (messages) => messages.map((m) => m.content).join("\n\n");
3084
- var formatMessagesForDisplay = (messages) => messages.map((m) => `[${m.role.toUpperCase()}]: ${m.content}`).join("\n\n---\n\n");
3489
+ function isSupportedAspectRatio(value) {
3490
+ return typeof value === "string" && SUPPORTED_ASPECT_RATIOS.includes(value);
3491
+ }
3492
+ function assertValidAspectRatio(value, fieldName = "aspect_ratio") {
3493
+ if (value === void 0 || value === null) return;
3494
+ if (!isSupportedAspectRatio(value)) {
3495
+ throw new ValidationError2({
3496
+ field: fieldName,
3497
+ reason: `must be one of ${SUPPORTED_ASPECT_RATIOS.join(", ")}`,
3498
+ receivedValue: value
3499
+ });
3500
+ }
3501
+ }
3085
3502
 
3086
- // src/providers/groq/groq-structured-json.ts
3087
- async function requestStructuredJson(client, request) {
3088
- const chatRequest = {
3089
- model: request.model,
3090
- messages: [createUserMessage(request.instructions)],
3091
- temperature: request.temperature,
3092
- max_tokens: request.maxTokens,
3093
- response_format: { type: "json_object" },
3094
- ...request.reasoningEffort !== void 0 ? { reasoning_effort: request.reasoningEffort } : {}
3095
- };
3096
- const response = await client.postChatCompletion(chatRequest);
3097
- const content = response.choices[0]?.message?.content ?? "";
3098
- const candidate = extractJsonCandidate(content);
3099
- try {
3100
- return JSON.parse(candidate);
3101
- } catch (err) {
3102
- throw new GroqError(
3103
- "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
3104
- "model response contained no parsable json object",
3105
- err instanceof Error ? err : void 0
3106
- );
3503
+ // src/providers/pruna/client/core/validation/resolution-validator.ts
3504
+ var SUPPORTED_RESOLUTIONS = ["720p", "1080p"];
3505
+ function isSupportedResolution(value) {
3506
+ return typeof value === "string" && SUPPORTED_RESOLUTIONS.includes(value);
3507
+ }
3508
+ function assertValidResolution(value, fieldName = "resolution") {
3509
+ if (value === void 0 || value === null) return;
3510
+ if (!isSupportedResolution(value)) {
3511
+ throw new ValidationError2({
3512
+ field: fieldName,
3513
+ reason: `must be one of ${SUPPORTED_RESOLUTIONS.join(", ")}`,
3514
+ receivedValue: value
3515
+ });
3107
3516
  }
3108
3517
  }
3109
3518
 
3110
- // src/providers/groq/text-generation.service.ts
3111
- var STRUCTURED_OUTPUT_TEMPERATURE = 0.1;
3112
- var TextGenerationService = class {
3113
- constructor(httpClient, cache = new CacheManager(), defaultModel = DEFAULT_MODELS.TEXT) {
3114
- this.httpClient = httpClient;
3115
- this.cache = cache;
3116
- this.defaultModel = defaultModel;
3519
+ // src/providers/pruna/client/core/validation/duration-validator.ts
3520
+ var MIN_VIDEO_DURATION_SECONDS = 1;
3521
+ var MAX_VIDEO_DURATION_SECONDS = 30;
3522
+ function isValidDuration(value) {
3523
+ return typeof value === "number" && Number.isFinite(value) && value >= MIN_VIDEO_DURATION_SECONDS && value <= MAX_VIDEO_DURATION_SECONDS && Number.isInteger(value);
3524
+ }
3525
+ function assertValidDuration(value, fieldName = "duration") {
3526
+ if (value === void 0 || value === null) return;
3527
+ if (!isValidDuration(value)) {
3528
+ throw new ValidationError2({
3529
+ field: fieldName,
3530
+ reason: `must be an integer between ${MIN_VIDEO_DURATION_SECONDS} and ${MAX_VIDEO_DURATION_SECONDS} seconds`,
3531
+ receivedValue: value
3532
+ });
3117
3533
  }
3118
- httpClient;
3119
- cache;
3120
- defaultModel;
3121
- id = "groq-text-generation";
3122
- name = "Groq Text Generation";
3123
- capabilities = ["text"];
3124
- jsonParser = new JsonResponseParser();
3125
- generateCompletion(prompt, options = {}) {
3126
- const model = options.model ?? this.defaultModel;
3127
- return this.executeWithCache(
3128
- {
3129
- type: CACHE_KEY_TYPE_COMPLETION,
3130
- model,
3131
- prompt,
3132
- config: options.generationConfig
3133
- },
3134
- async () => {
3135
- const request = GroqChatRequestBuilder.for(model).withMessages([createUserMessage(prompt)]).withConfig(options.generationConfig).build();
3136
- const response = await this.httpClient.postChatCompletion(request);
3137
- return this.extractContent(response.choices[0]?.message?.content);
3534
+ }
3535
+
3536
+ // src/providers/pruna/client/core/validation/prompt-validator.ts
3537
+ var MIN_PROMPT_LENGTH = 1;
3538
+ var MAX_PROMPT_LENGTH = 4e3;
3539
+ function assertValidPrompt(value, fieldName = "prompt") {
3540
+ if (typeof value !== "string") {
3541
+ throw new ValidationError2({
3542
+ field: fieldName,
3543
+ reason: "must be a string",
3544
+ receivedValue: typeof value
3545
+ });
3546
+ }
3547
+ if (value.trim().length < MIN_PROMPT_LENGTH) {
3548
+ throw new ValidationError2({
3549
+ field: fieldName,
3550
+ reason: "must not be empty or whitespace-only",
3551
+ receivedValue: value
3552
+ });
3553
+ }
3554
+ if (value.length > MAX_PROMPT_LENGTH) {
3555
+ throw new ValidationError2({
3556
+ field: fieldName,
3557
+ reason: `exceeds maximum length of ${MAX_PROMPT_LENGTH} characters`,
3558
+ receivedValue: value.length
3559
+ });
3560
+ }
3561
+ }
3562
+
3563
+ // src/providers/pruna/client/core/validation/seed-validator.ts
3564
+ var MIN_SEED = -2147483648;
3565
+ var MAX_SEED = 2147483647;
3566
+ function isValidSeed(value) {
3567
+ return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= MIN_SEED && value <= MAX_SEED;
3568
+ }
3569
+ function assertValidSeed(value, fieldName = "seed") {
3570
+ if (value === void 0 || value === null) return;
3571
+ if (!isValidSeed(value)) {
3572
+ throw new ValidationError2({
3573
+ field: fieldName,
3574
+ reason: `must be an integer in [${MIN_SEED}, ${MAX_SEED}]`,
3575
+ receivedValue: value
3576
+ });
3577
+ }
3578
+ }
3579
+
3580
+ // src/providers/pruna/client/core/protocol/input-type-guard.ts
3581
+ function isTextToImageInput(input) {
3582
+ return input.model === "p-image";
3583
+ }
3584
+ function isImageToImageInput(input) {
3585
+ return input.model === "p-image-edit";
3586
+ }
3587
+ function isImageToVideoInput(input) {
3588
+ return input.model === "p-video";
3589
+ }
3590
+
3591
+ // src/providers/pruna/client/core/utils/request-deduplicator.ts
3592
+ var RequestDeduplicator2 = class {
3593
+ pendingRequests = /* @__PURE__ */ new Map();
3594
+ ttl;
3595
+ constructor(ttl = DEFAULT_DEDUPLICATOR_TTL_MS) {
3596
+ this.ttl = ttl;
3597
+ }
3598
+ execute(key, fn) {
3599
+ const existing = this.pendingRequests.get(key);
3600
+ if (existing) {
3601
+ if (Date.now() - existing.timestamp < this.ttl) {
3602
+ return existing.promise;
3138
3603
  }
3139
- );
3604
+ this.pendingRequests.delete(key);
3605
+ }
3606
+ const promise = fn().finally(() => {
3607
+ setTimeout(() => {
3608
+ this.pendingRequests.delete(key);
3609
+ }, DEDUPLICATOR_CLEANUP_GRACE_MS);
3610
+ });
3611
+ this.pendingRequests.set(key, { promise, timestamp: Date.now() });
3612
+ return promise;
3140
3613
  }
3141
- generateChatCompletion(messages, options = {}) {
3142
- const model = options.model ?? this.defaultModel;
3143
- return this.executeWithCache(
3144
- {
3145
- type: CACHE_KEY_TYPE_CHAT,
3146
- model,
3147
- messages: [...messages],
3148
- config: options.generationConfig
3149
- },
3150
- async () => {
3151
- const request = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig(options.generationConfig).build();
3152
- const response = await this.httpClient.postChatCompletion(request);
3153
- return this.extractContent(response.choices[0]?.message?.content);
3614
+ clear() {
3615
+ this.pendingRequests.clear();
3616
+ }
3617
+ size() {
3618
+ this.purgeExpired();
3619
+ return this.pendingRequests.size;
3620
+ }
3621
+ purgeExpired() {
3622
+ const now = Date.now();
3623
+ for (const [key, value] of this.pendingRequests) {
3624
+ if (now - value.timestamp >= this.ttl) {
3625
+ this.pendingRequests.delete(key);
3154
3626
  }
3627
+ }
3628
+ }
3629
+ };
3630
+ var globalDeduplicator = new RequestDeduplicator2();
3631
+
3632
+ // src/providers/pruna/client/generation/builders/text-to-image.builder.ts
3633
+ var TextToImageInputBuilder = class {
3634
+ model = "p-image";
3635
+ build(input, _context) {
3636
+ const payload = {
3637
+ prompt: input.prompt,
3638
+ aspect_ratio: input.aspect_ratio ?? DEFAULT_ASPECT_RATIO
3639
+ };
3640
+ if (input.seed !== void 0) {
3641
+ payload.seed = input.seed;
3642
+ }
3643
+ return Promise.resolve(payload);
3644
+ }
3645
+ };
3646
+
3647
+ // src/providers/pruna/client/generation/builders/image-to-image.builder.ts
3648
+ var ImageToImageInputBuilder = class {
3649
+ model = "p-image-edit";
3650
+ build(input, _context) {
3651
+ const payload = {
3652
+ images: [stripBase64Prefix(input.image)],
3653
+ prompt: input.prompt,
3654
+ aspect_ratio: input.aspect_ratio ?? DEFAULT_ASPECT_RATIO
3655
+ };
3656
+ if (input.seed !== void 0) {
3657
+ payload.seed = input.seed;
3658
+ }
3659
+ return Promise.resolve(payload);
3660
+ }
3661
+ };
3662
+
3663
+ // src/providers/pruna/client/generation/builders/image-to-video.builder.ts
3664
+ var ImageToVideoInputBuilder = class {
3665
+ model = "p-video";
3666
+ async build(input, context) {
3667
+ if (context.signal?.aborted) {
3668
+ throw new CancellationError();
3669
+ }
3670
+ const fileUrl = await uploadImage(
3671
+ input.image,
3672
+ context.apiKey,
3673
+ context.onProgress
3155
3674
  );
3675
+ return {
3676
+ image: fileUrl,
3677
+ prompt: input.prompt,
3678
+ duration: input.duration ?? P_VIDEO_DEFAULTS.duration,
3679
+ resolution: input.resolution ?? P_VIDEO_DEFAULTS.resolution,
3680
+ fps: P_VIDEO_DEFAULTS.fps,
3681
+ draft: input.draft ?? P_VIDEO_DEFAULTS.draft,
3682
+ aspect_ratio: input.aspect_ratio ?? DEFAULT_ASPECT_RATIO,
3683
+ prompt_upsampling: P_VIDEO_DEFAULTS.promptUpsampling
3684
+ };
3156
3685
  }
3157
- generateStructured(promptOrRequest, options) {
3158
- if (typeof promptOrRequest === "string") {
3159
- const model2 = options?.model ?? this.defaultModel;
3160
- const jsonPrompt = StructuredPromptBuilder.for(promptOrRequest).withSchema(options?.schema).build();
3161
- const request = GroqChatRequestBuilder.for(model2).withMessages([createUserMessage(jsonPrompt)]).withConfig({
3162
- ...options?.generationConfig,
3163
- temperature: STRUCTURED_OUTPUT_TEMPERATURE
3164
- }).build();
3165
- return this.httpClient.postChatCompletion(request).then((response) => {
3166
- const content = this.extractContent(response.choices[0]?.message?.content);
3167
- return this.jsonParser.parse(content);
3686
+ };
3687
+
3688
+ // src/providers/pruna/client/generation/builders/model-input-builder.ts
3689
+ var ModelInputBuilderRegistry = class {
3690
+ builders = /* @__PURE__ */ new Map();
3691
+ register(builder) {
3692
+ this.builders.set(builder.model, builder);
3693
+ return this;
3694
+ }
3695
+ resolve(model) {
3696
+ const builder = this.builders.get(model);
3697
+ if (!builder) {
3698
+ throw new Error(`No input builder registered for model: ${model}`);
3699
+ }
3700
+ return builder;
3701
+ }
3702
+ };
3703
+
3704
+ // src/providers/pruna/client/generation/builders/builder-factory.ts
3705
+ function createDefaultBuilderRegistry() {
3706
+ return new ModelInputBuilderRegistry().register(new TextToImageInputBuilder()).register(new ImageToImageInputBuilder()).register(new ImageToVideoInputBuilder());
3707
+ }
3708
+
3709
+ // src/providers/pruna/client/generation/services/generation.service.ts
3710
+ var builderRegistry = createDefaultBuilderRegistry();
3711
+ function validateGenerateInput(input) {
3712
+ assertValidPrompt(input.prompt, `${input.model}.prompt`);
3713
+ assertValidAspectRatio(input.aspect_ratio, `${input.model}.aspect_ratio`);
3714
+ if (isTextToImageInput(input)) {
3715
+ assertValidSeed(input.seed, `${input.model}.seed`);
3716
+ } else if (isImageToImageInput(input)) {
3717
+ assertSecureImageSource(input.image, `${input.model}.image`);
3718
+ assertValidSeed(input.seed, `${input.model}.seed`);
3719
+ } else if (isImageToVideoInput(input)) {
3720
+ assertSecureImageSource(input.image, `${input.model}.image`);
3721
+ assertValidDuration(input.duration, `${input.model}.duration`);
3722
+ assertValidResolution(input.resolution, `${input.model}.resolution`);
3723
+ }
3724
+ }
3725
+ async function generate(apiKey, input, options = {}) {
3726
+ assertValidApiKey(apiKey);
3727
+ validateGenerateInput(input);
3728
+ const { signal, onProgress, useCache = true } = options;
3729
+ if (useCache) {
3730
+ const cacheKey = deriveGenerationCacheKey(apiKey, input);
3731
+ return globalDeduplicator.execute(
3732
+ cacheKey,
3733
+ () => generateInternal(apiKey, input, { signal, onProgress })
3734
+ );
3735
+ }
3736
+ return generateInternal(apiKey, input, { signal, onProgress });
3737
+ }
3738
+ async function generateInternal(apiKey, input, options) {
3739
+ const { signal, onProgress } = options;
3740
+ const builder = builderRegistry.resolve(input.model);
3741
+ const modelInput = await builder.build(input, { apiKey, signal, onProgress });
3742
+ const response = await submitPrediction(input.model, modelInput, apiKey, signal, onProgress);
3743
+ const syncUri = extractUri(response);
3744
+ if (syncUri) {
3745
+ return { url: resolveUri(syncUri), model: input.model };
3746
+ }
3747
+ const pollUrl = response.get_url ?? response.status_url;
3748
+ if (!pollUrl) {
3749
+ throw new Error("Pruna API returned no result and no polling URL.");
3750
+ }
3751
+ const url = await pollForResult(
3752
+ pollUrl,
3753
+ apiKey,
3754
+ POLL_DEFAULTS.maxAttempts,
3755
+ POLL_DEFAULTS.intervalMs,
3756
+ signal,
3757
+ onProgress
3758
+ );
3759
+ return { url, model: input.model };
3760
+ }
3761
+ async function generateImageThenVideo(apiKey, input, options = {}) {
3762
+ assertValidApiKey(apiKey);
3763
+ assertValidPrompt(input.prompt, "text-to-video.prompt");
3764
+ assertValidAspectRatio(input.aspect_ratio, "text-to-video.aspect_ratio");
3765
+ assertValidDuration(input.duration, "text-to-video.duration");
3766
+ assertValidResolution(input.resolution, "text-to-video.resolution");
3767
+ const { signal, onProgress } = options;
3768
+ const aspectRatio = input.aspect_ratio ?? DEFAULT_ASPECT_RATIO;
3769
+ onProgress?.("predicting", 1);
3770
+ const imageResult = await generate(
3771
+ apiKey,
3772
+ { model: "p-image", prompt: input.prompt, aspect_ratio: aspectRatio },
3773
+ { signal }
3774
+ );
3775
+ onProgress?.("predicting", 2);
3776
+ return generate(
3777
+ apiKey,
3778
+ {
3779
+ model: "p-video",
3780
+ prompt: input.prompt,
3781
+ image: imageResult.url,
3782
+ duration: input.duration ?? P_VIDEO_DEFAULTS.duration,
3783
+ resolution: input.resolution ?? P_VIDEO_DEFAULTS.resolution,
3784
+ aspect_ratio: aspectRatio,
3785
+ draft: input.draft ?? P_VIDEO_DEFAULTS.draft
3786
+ },
3787
+ { signal, onProgress }
3788
+ );
3789
+ }
3790
+
3791
+ // src/providers/pruna/pruna-generate.ts
3792
+ async function* generateWithPruna(request, apiKey, signal, onProgress) {
3793
+ const stages = [];
3794
+ let notify = null;
3795
+ let done = false;
3796
+ const wake = () => {
3797
+ const fn = notify;
3798
+ if (fn) {
3799
+ notify = null;
3800
+ fn();
3801
+ }
3802
+ };
3803
+ const handleProgress = (stage) => {
3804
+ stages.push({ stage });
3805
+ onProgress?.(stage);
3806
+ wake();
3807
+ };
3808
+ const promise = (async () => {
3809
+ try {
3810
+ const model = request.model ?? (request.image ? "p-image-edit" : "p-image");
3811
+ if (model === "p-video" && !request.image && request.prompt) {
3812
+ const videoRes = await generateImageThenVideo(
3813
+ apiKey,
3814
+ {
3815
+ prompt: request.prompt,
3816
+ duration: request.duration ?? 5,
3817
+ aspect_ratio: request.aspectRatio ?? request.aspect_ratio ?? "16:9"
3818
+ },
3819
+ { signal, onProgress: handleProgress }
3820
+ );
3821
+ stages.push({
3822
+ stage: "polling",
3823
+ result: { url: videoRes.url, model: "p-video" }
3824
+ });
3825
+ return;
3826
+ }
3827
+ const input = {
3828
+ model,
3829
+ prompt: request.prompt ?? "",
3830
+ aspect_ratio: request.aspectRatio ?? request.aspect_ratio ?? "1:1",
3831
+ ...request.image ? { image: request.image } : {},
3832
+ ...request.duration ? { duration: request.duration } : {},
3833
+ ...request.resolution ? { resolution: request.resolution } : {},
3834
+ ...request.seed !== void 0 ? { seed: request.seed } : {}
3835
+ };
3836
+ const res = await generate(apiKey, input, {
3837
+ signal,
3838
+ onProgress: handleProgress
3839
+ });
3840
+ stages.push({
3841
+ stage: "polling",
3842
+ result: { url: res.url, model: res.model }
3843
+ });
3844
+ } catch (err) {
3845
+ stages.push({
3846
+ stage: "processing",
3847
+ error: err instanceof Error ? err : new Error(String(err))
3168
3848
  });
3849
+ } finally {
3850
+ done = true;
3851
+ wake();
3169
3852
  }
3170
- const req = promptOrRequest;
3171
- const model = req.model ?? (req.tier ? this.resolveModel(req.tier) : this.defaultModel);
3172
- const schemaObj = req.schema;
3173
- const messages = [];
3174
- if (req.systemPrompt) {
3175
- messages.push({ role: "system", content: req.systemPrompt });
3853
+ })();
3854
+ while (!done || stages.length > 0) {
3855
+ if (stages.length === 0 && !done) {
3856
+ await new Promise((resolve) => {
3857
+ notify = resolve;
3858
+ });
3859
+ notify = null;
3176
3860
  }
3177
- for (const m of req.messages) {
3178
- messages.push({ role: m.role, content: m.content });
3861
+ while (stages.length > 0) {
3862
+ const nextStage = stages.shift();
3863
+ if (!nextStage) continue;
3864
+ yield nextStage;
3865
+ if (nextStage.error) {
3866
+ throw nextStage.error;
3867
+ }
3179
3868
  }
3180
- const lastMsg = messages[messages.length - 1];
3181
- if (lastMsg) {
3182
- const structuredPrompt = StructuredPromptBuilder.for(lastMsg.content).withSchema(schemaObj).build();
3183
- messages[messages.length - 1] = { ...lastMsg, content: structuredPrompt };
3869
+ }
3870
+ await promise;
3871
+ }
3872
+
3873
+ // src/providers/pruna/pruna.provider.ts
3874
+ var PrunaProvider = class {
3875
+ constructor(config) {
3876
+ this.config = config;
3877
+ }
3878
+ config;
3879
+ id = "pruna";
3880
+ name = "Pruna AI";
3881
+ capabilities = ["image", "video"];
3882
+ async healthCheck() {
3883
+ return this.config.apiKey ? "healthy" : "unavailable";
3884
+ }
3885
+ async estimateCost(_request) {
3886
+ return 1;
3887
+ }
3888
+ async generateImage(request) {
3889
+ let finalUrl;
3890
+ const model = this.config.models?.textToImage;
3891
+ for await (const stage of generateWithPruna(
3892
+ { prompt: request.prompt, model, quality: request.quality },
3893
+ this.config.apiKey
3894
+ )) {
3895
+ if (stage.error) throw stage.error;
3896
+ if (stage.result?.url) finalUrl = stage.result.url;
3184
3897
  }
3185
- const groqReq = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig({
3186
- temperature: req.temperature ?? STRUCTURED_OUTPUT_TEMPERATURE,
3187
- maxTokens: req.maxTokens
3188
- }).build();
3189
- return this.httpClient.postChatCompletion(groqReq).then((response) => {
3190
- const content = this.extractContent(response.choices[0]?.message?.content);
3191
- const parsed = this.jsonParser.parse(content);
3192
- const genResult = {
3193
- text: content,
3194
- model: response.model,
3195
- providerId: this.id,
3196
- finishReason: response.choices[0]?.finish_reason ?? "stop",
3197
- usage: response.usage ? {
3198
- inputTokens: response.usage.prompt_tokens,
3199
- outputTokens: response.usage.completion_tokens,
3200
- totalTokens: response.usage.total_tokens
3201
- } : void 0
3202
- };
3203
- return { value: parsed, result: genResult };
3898
+ if (!finalUrl) {
3899
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No image generated by Pruna AI");
3900
+ }
3901
+ return {
3902
+ id: Date.now().toString(),
3903
+ type: "image",
3904
+ url: finalUrl,
3905
+ metadata: { provider: this.id, model: model ?? "p-image" },
3906
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
3907
+ };
3908
+ }
3909
+ async generateVideo(request) {
3910
+ let finalUrl;
3911
+ const model = this.config.models?.textToVideo;
3912
+ for await (const stage of generateWithPruna(
3913
+ { prompt: request.prompt, model, duration: request.duration, aspectRatio: request.aspectRatio },
3914
+ this.config.apiKey
3915
+ )) {
3916
+ if (stage.error) throw stage.error;
3917
+ if (stage.result?.url) finalUrl = stage.result.url;
3918
+ }
3919
+ if (!finalUrl) {
3920
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated by Pruna AI");
3921
+ }
3922
+ return {
3923
+ id: Date.now().toString(),
3924
+ type: "video",
3925
+ url: finalUrl,
3926
+ metadata: { provider: this.id, model: model ?? "p-video" },
3927
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
3928
+ };
3929
+ }
3930
+ async imageToVideo(request) {
3931
+ let finalUrl;
3932
+ for await (const stage of generateWithPruna(
3933
+ { image: request.imageUrl, prompt: request.prompt, duration: request.duration, motion: request.motion },
3934
+ this.config.apiKey
3935
+ )) {
3936
+ if (stage.error) throw stage.error;
3937
+ if (stage.result?.url) finalUrl = stage.result.url;
3938
+ }
3939
+ if (!finalUrl) {
3940
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated from image by Pruna AI");
3941
+ }
3942
+ return {
3943
+ id: Date.now().toString(),
3944
+ type: "video",
3945
+ url: finalUrl,
3946
+ metadata: { provider: this.id, model: "p-image-to-video" },
3947
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
3948
+ };
3949
+ }
3950
+ async videoToVideo(_request) {
3951
+ throw new AIError("CLIENT_ERROR" /* CLIENT_ERROR */, "Pruna AI provider does not support video-to-video conversion");
3952
+ }
3953
+ };
3954
+ function createPrunaProvider(config) {
3955
+ return new PrunaProvider(config);
3956
+ }
3957
+
3958
+ // src/providers/factory/create-ai.ts
3959
+ function createAI(config = {}) {
3960
+ const factory = new ProviderFactory({
3961
+ priority: config.priority,
3962
+ fallbackEnabled: config.fallbackEnabled,
3963
+ defaultTier: config.defaultTier
3964
+ });
3965
+ if (config.anthropic?.apiKey) {
3966
+ factory.register(new AnthropicProvider(config.anthropic));
3967
+ }
3968
+ if (config.groq?.apiKey) {
3969
+ factory.register(new GroqProvider(config.groq));
3970
+ }
3971
+ if (config.gemini?.apiKey) {
3972
+ factory.register(new GeminiProvider({ ...config.gemini, enabled: true }));
3973
+ }
3974
+ if (config.fal?.apiKey) {
3975
+ factory.register(new FalProvider({ ...config.fal, enabled: true }));
3976
+ }
3977
+ if (config.pruna?.apiKey) {
3978
+ factory.register(new PrunaProvider({ ...config.pruna, enabled: true }));
3979
+ }
3980
+ return factory;
3981
+ }
3982
+
3983
+ // src/providers/groq/groq-messages.ts
3984
+ var createUserMessage = (content) => ({
3985
+ role: "user",
3986
+ content
3987
+ });
3988
+ var createAssistantMessage = (content) => ({
3989
+ role: "assistant",
3990
+ content
3991
+ });
3992
+ var createSystemMessage = (content) => ({
3993
+ role: "system",
3994
+ content
3995
+ });
3996
+ var createMessage = (role, content) => ({
3997
+ role,
3998
+ content
3999
+ });
4000
+ var promptToMessages = (prompt) => [
4001
+ createUserMessage(prompt)
4002
+ ];
4003
+ var extractTextFromMessages = (messages) => messages.map((m) => m.content).join("\n\n");
4004
+ var formatMessagesForDisplay = (messages) => messages.map((m) => `[${m.role.toUpperCase()}]: ${m.content}`).join("\n\n---\n\n");
4005
+
4006
+ // src/providers/groq/groq-structured-json.ts
4007
+ async function requestStructuredJson(client, request) {
4008
+ const chatRequest = {
4009
+ model: request.model,
4010
+ messages: [createUserMessage(request.instructions)],
4011
+ temperature: request.temperature,
4012
+ max_tokens: request.maxTokens,
4013
+ response_format: { type: "json_object" },
4014
+ ...request.reasoningEffort !== void 0 ? { reasoning_effort: request.reasoningEffort } : {}
4015
+ };
4016
+ const response = await client.postChatCompletion(chatRequest);
4017
+ const content = response.choices[0]?.message?.content ?? "";
4018
+ const candidate = extractJsonCandidate(content);
4019
+ try {
4020
+ return JSON.parse(candidate);
4021
+ } catch (err) {
4022
+ throw new GroqError(
4023
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
4024
+ "model response contained no parsable json object",
4025
+ err instanceof Error ? err : void 0
4026
+ );
4027
+ }
4028
+ }
4029
+
4030
+ // src/providers/groq/text-generation.service.ts
4031
+ var STRUCTURED_OUTPUT_TEMPERATURE = 0.1;
4032
+ var TextGenerationService = class {
4033
+ constructor(httpClient, cache = new CacheManager(), defaultModel = DEFAULT_MODELS.TEXT) {
4034
+ this.httpClient = httpClient;
4035
+ this.cache = cache;
4036
+ this.defaultModel = defaultModel;
4037
+ }
4038
+ httpClient;
4039
+ cache;
4040
+ defaultModel;
4041
+ id = "groq-text-generation";
4042
+ name = "Groq Text Generation";
4043
+ capabilities = ["text"];
4044
+ jsonParser = new JsonResponseParser();
4045
+ generateCompletion(prompt, options = {}) {
4046
+ const model = options.model ?? this.defaultModel;
4047
+ return this.executeWithCache(
4048
+ {
4049
+ type: CACHE_KEY_TYPE_COMPLETION,
4050
+ model,
4051
+ prompt,
4052
+ config: options.generationConfig
4053
+ },
4054
+ async () => {
4055
+ const request = GroqChatRequestBuilder.for(model).withMessages([createUserMessage(prompt)]).withConfig(options.generationConfig).build();
4056
+ const response = await this.httpClient.postChatCompletion(request);
4057
+ return this.extractContent(response.choices[0]?.message?.content);
4058
+ }
4059
+ );
4060
+ }
4061
+ generateChatCompletion(messages, options = {}) {
4062
+ const model = options.model ?? this.defaultModel;
4063
+ return this.executeWithCache(
4064
+ {
4065
+ type: CACHE_KEY_TYPE_CHAT,
4066
+ model,
4067
+ messages: [...messages],
4068
+ config: options.generationConfig
4069
+ },
4070
+ async () => {
4071
+ const request = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig(options.generationConfig).build();
4072
+ const response = await this.httpClient.postChatCompletion(request);
4073
+ return this.extractContent(response.choices[0]?.message?.content);
4074
+ }
4075
+ );
4076
+ }
4077
+ generateStructured(promptOrRequest, options) {
4078
+ if (typeof promptOrRequest === "string") {
4079
+ const model2 = options?.model ?? this.defaultModel;
4080
+ const jsonPrompt = StructuredPromptBuilder.for(promptOrRequest).withSchema(options?.schema).build();
4081
+ const request = GroqChatRequestBuilder.for(model2).withMessages([createUserMessage(jsonPrompt)]).withConfig({
4082
+ ...options?.generationConfig,
4083
+ temperature: STRUCTURED_OUTPUT_TEMPERATURE
4084
+ }).build();
4085
+ return this.httpClient.postChatCompletion(request).then((response) => {
4086
+ const content = this.extractContent(response.choices[0]?.message?.content);
4087
+ return this.jsonParser.parse(content);
4088
+ });
4089
+ }
4090
+ const req = promptOrRequest;
4091
+ const model = req.model ?? (req.tier ? this.resolveModel(req.tier) : this.defaultModel);
4092
+ const schemaObj = req.schema;
4093
+ const messages = [];
4094
+ if (req.systemPrompt) {
4095
+ messages.push({ role: "system", content: req.systemPrompt });
4096
+ }
4097
+ for (const m of req.messages) {
4098
+ messages.push({ role: m.role, content: m.content });
4099
+ }
4100
+ const lastMsg = messages[messages.length - 1];
4101
+ if (lastMsg) {
4102
+ const structuredPrompt = StructuredPromptBuilder.for(lastMsg.content).withSchema(schemaObj).build();
4103
+ messages[messages.length - 1] = { ...lastMsg, content: structuredPrompt };
4104
+ }
4105
+ const groqReq = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig({
4106
+ temperature: req.temperature ?? STRUCTURED_OUTPUT_TEMPERATURE,
4107
+ maxTokens: req.maxTokens
4108
+ }).build();
4109
+ return this.httpClient.postChatCompletion(groqReq).then((response) => {
4110
+ const content = this.extractContent(response.choices[0]?.message?.content);
4111
+ const parsed = this.jsonParser.parse(content);
4112
+ const genResult = {
4113
+ text: content,
4114
+ model: response.model,
4115
+ providerId: this.id,
4116
+ finishReason: response.choices[0]?.finish_reason ?? "stop",
4117
+ usage: response.usage ? {
4118
+ inputTokens: response.usage.prompt_tokens,
4119
+ outputTokens: response.usage.completion_tokens,
4120
+ totalTokens: response.usage.total_tokens
4121
+ } : void 0
4122
+ };
4123
+ return { value: parsed, result: genResult };
4124
+ });
4125
+ }
4126
+ streamCompletion(prompt, callbacks, options = {}) {
4127
+ return this.streamChatCompletion([createUserMessage(prompt)], callbacks, options);
4128
+ }
4129
+ async streamChatCompletion(messages, callbacks, options = {}) {
4130
+ const model = options.model ?? this.defaultModel;
4131
+ const request = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig(options.generationConfig).withStream(true).build();
4132
+ const parser = new StreamingResponseParser(
4133
+ {
4134
+ onChunk: (chunk) => callbacks.onChunk?.(chunk)
4135
+ },
4136
+ extractContentFromSsePayload
4137
+ );
4138
+ try {
4139
+ const stream = await this.httpClient.streamChatCompletion(request, {
4140
+ signal: options.signal
4141
+ });
4142
+ const fullText = await parser.parse(stream);
4143
+ callbacks.onComplete?.(fullText);
4144
+ return fullText;
4145
+ } catch (error) {
4146
+ const err = error instanceof Error ? error : new Error(String(error));
4147
+ callbacks.onError?.(err);
4148
+ throw err;
4149
+ }
4150
+ }
4151
+ clearCache() {
4152
+ this.cache.clear();
4153
+ }
4154
+ // --- ITextProvider implementation ---
4155
+ resolveModel(tier) {
4156
+ return GROQ_TIERS[tier] ?? this.defaultModel;
4157
+ }
4158
+ async generate(request) {
4159
+ const messages = [];
4160
+ if (request.systemPrompt) {
4161
+ messages.push({ role: "system", content: request.systemPrompt });
4162
+ }
4163
+ for (const m of request.messages) {
4164
+ messages.push({ role: m.role, content: m.content });
4165
+ }
4166
+ const model = request.model ?? (request.tier ? this.resolveModel(request.tier) : this.defaultModel);
4167
+ const groqReq = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig({
4168
+ temperature: request.temperature,
4169
+ maxTokens: request.maxTokens
4170
+ }).build();
4171
+ const resp = await this.httpClient.postChatCompletion(groqReq);
4172
+ const content = resp.choices[0]?.message?.content ?? "";
4173
+ return {
4174
+ text: content,
4175
+ finishReason: resp.choices[0]?.finish_reason ?? "stop",
4176
+ model: resp.model,
4177
+ providerId: this.id,
4178
+ usage: resp.usage ? {
4179
+ inputTokens: resp.usage.prompt_tokens,
4180
+ outputTokens: resp.usage.completion_tokens,
4181
+ totalTokens: resp.usage.total_tokens
4182
+ } : void 0
4183
+ };
4184
+ }
4185
+ async stream(request, callbacks) {
4186
+ const messages = [];
4187
+ if (request.systemPrompt) {
4188
+ messages.push({ role: "system", content: request.systemPrompt });
4189
+ }
4190
+ for (const m of request.messages) {
4191
+ messages.push({ role: m.role, content: m.content });
4192
+ }
4193
+ const model = request.model ?? (request.tier ? this.resolveModel(request.tier) : this.defaultModel);
4194
+ const text = await this.streamChatCompletion(
4195
+ messages,
4196
+ {
4197
+ onChunk: callbacks.onChunk,
4198
+ onError: callbacks.onError
4199
+ },
4200
+ {
4201
+ model,
4202
+ signal: request.signal,
4203
+ generationConfig: {
4204
+ temperature: request.temperature,
4205
+ maxTokens: request.maxTokens
4206
+ }
4207
+ }
4208
+ );
4209
+ const result = {
4210
+ text,
4211
+ finishReason: "stop",
4212
+ model,
4213
+ providerId: this.id
4214
+ };
4215
+ callbacks.onComplete?.(result);
4216
+ return result;
4217
+ }
4218
+ async countTokens(text) {
4219
+ return Math.max(1, Math.ceil(text.length / 4));
4220
+ }
4221
+ async estimateCost(_request) {
4222
+ return 0;
4223
+ }
4224
+ async healthCheck() {
4225
+ try {
4226
+ await this.generateCompletion("ping", {
4227
+ generationConfig: { maxTokens: 1 }
4228
+ });
4229
+ return "healthy";
4230
+ } catch {
4231
+ return "unavailable";
4232
+ }
4233
+ }
4234
+ async executeWithCache(params, factory) {
4235
+ const key = this.cache.generateKey(params);
4236
+ const cached = this.cache.get(key);
4237
+ if (cached !== null) {
4238
+ return cached;
4239
+ }
4240
+ const result = await factory();
4241
+ this.cache.set(key, result);
4242
+ return result;
4243
+ }
4244
+ extractContent(content) {
4245
+ if (!content) {
4246
+ throw new GroqError(
4247
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
4248
+ "No content generated from Groq API"
4249
+ );
4250
+ }
4251
+ return content;
4252
+ }
4253
+ };
4254
+
4255
+ // src/core/calculations/random-base36-suffix.ts
4256
+ var BASE_36_RADIX = 36;
4257
+ var DECIMAL_OFFSET = 2;
4258
+ function generateRandomBase36Suffix(length, random = Math.random) {
4259
+ if (length <= 0) return "";
4260
+ const raw = random().toString(BASE_36_RADIX).slice(DECIMAL_OFFSET);
4261
+ if (raw.length >= length) return raw.slice(0, length);
4262
+ let result = raw;
4263
+ while (result.length < length) {
4264
+ result += random().toString(BASE_36_RADIX).slice(DECIMAL_OFFSET);
4265
+ }
4266
+ return result.slice(0, length);
4267
+ }
4268
+
4269
+ // src/chat/utils/chat-id-generator.ts
4270
+ var ID_PART_DELIMITER = "-";
4271
+ var ChatIdGenerator = class {
4272
+ counter = 0;
4273
+ randomLength;
4274
+ constructor(randomLength = CHAT_MESSAGE_ID_SUFFIX_LENGTH) {
4275
+ this.randomLength = randomLength;
4276
+ }
4277
+ next(sender) {
4278
+ this.counter += 1;
4279
+ const random = generateRandomBase36Suffix(this.randomLength);
4280
+ return [
4281
+ MESSAGE_ID_PREFIX,
4282
+ Date.now(),
4283
+ sender,
4284
+ this.counter,
4285
+ random
4286
+ ].join(ID_PART_DELIMITER);
4287
+ }
4288
+ };
4289
+ var chatIdGenerator = new ChatIdGenerator();
4290
+
4291
+ // src/chat/utils/message-formatter.ts
4292
+ var MessageFormatter = class {
4293
+ constructor(idGenerator = chatIdGenerator) {
4294
+ this.idGenerator = idGenerator;
4295
+ }
4296
+ idGenerator;
4297
+ /** Maps chat-domain history to neutral core messages; system senders are
4298
+ * dropped — the system prompt travels on the request's own slot. */
4299
+ toCoreMessages(messages) {
4300
+ const coreMessages = [];
4301
+ for (const message of messages) {
4302
+ if (message.sender === "system") continue;
4303
+ coreMessages.push({
4304
+ role: message.sender === "user" ? USER_ROLE : ASSISTANT_ROLE,
4305
+ content: message.content
4306
+ });
4307
+ }
4308
+ return coreMessages;
4309
+ }
4310
+ toChatMessage(content, sender) {
4311
+ return {
4312
+ id: this.idGenerator.next(sender),
4313
+ sender,
4314
+ content: content.trim(),
4315
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4316
+ type: "text"
4317
+ };
4318
+ }
4319
+ };
4320
+ var messageFormatter = new MessageFormatter();
4321
+
4322
+ // src/chat/utils/prompt-strategy.ts
4323
+ var DEFAULT_PROMPT_STRATEGY = {
4324
+ resolve: (config) => {
4325
+ if (config.systemPrompt && config.systemPrompt.length > 0) {
4326
+ return config.systemPrompt;
4327
+ }
4328
+ return void 0;
4329
+ }
4330
+ };
4331
+
4332
+ // src/chat/services/chat.service.ts
4333
+ var DEFAULT_CONFIG = {
4334
+ temperature: 0.8,
4335
+ maxTokens: 500
4336
+ };
4337
+ var ChatService = class {
4338
+ constructor(engine) {
4339
+ this.engine = engine;
4340
+ }
4341
+ engine;
4342
+ config = DEFAULT_CONFIG;
4343
+ promptStrategy = DEFAULT_PROMPT_STRATEGY;
4344
+ storage = null;
4345
+ initialize(config) {
4346
+ this.config = { ...DEFAULT_CONFIG, ...config };
4347
+ }
4348
+ updateConfig(config) {
4349
+ this.config = { ...this.config, ...config };
4350
+ }
4351
+ setPromptStrategy(strategy) {
4352
+ this.promptStrategy = strategy;
4353
+ }
4354
+ setStorage(storage) {
4355
+ this.storage = storage;
4356
+ }
4357
+ async getConversation(conversationId) {
4358
+ const storage = this.requireStorage();
4359
+ const messages = await storage.getMessages(conversationId);
4360
+ if (messages.length === 0) return null;
4361
+ const last = messages[messages.length - 1];
4362
+ const first = messages[0];
4363
+ const companionId = String(first.metadata?.["companionId"] ?? "unknown");
4364
+ const companionName = String(first.metadata?.["companionName"] ?? "Unknown");
4365
+ return {
4366
+ id: conversationId,
4367
+ companionId,
4368
+ companionName,
4369
+ lastMessage: last,
4370
+ messageCount: messages.length,
4371
+ updatedAt: last.timestamp
4372
+ };
4373
+ }
4374
+ async getConversations() {
4375
+ if (this.storage && "getConversations" in this.storage && typeof this.storage.getConversations === "function") {
4376
+ return this.storage.getConversations();
4377
+ }
4378
+ return [];
4379
+ }
4380
+ async sendMessage(input, signal) {
4381
+ const userMessage = {
4382
+ ...messageFormatter.toChatMessage(input.text, "user"),
4383
+ type: input.type ?? "text",
4384
+ ...input.imageUrl !== void 0 ? { imageUrl: input.imageUrl } : {}
4385
+ };
4386
+ const aiResponse = await this.generateAIResponse(
4387
+ input.companionId,
4388
+ input.text,
4389
+ [],
4390
+ signal
4391
+ );
4392
+ return { userMessage, aiResponse };
4393
+ }
4394
+ async generateAIResponse(companionId, userMessage, context = [], signal) {
4395
+ const result = await this.run({
4396
+ ...this.buildRequest(userMessage, context, signal)
4397
+ });
4398
+ return {
4399
+ ...messageFormatter.toChatMessage(result.text, "assistant"),
4400
+ metadata: {
4401
+ companionId,
4402
+ model: result.model,
4403
+ usage: {
4404
+ input_tokens: result.usage?.inputTokens,
4405
+ output_tokens: result.usage?.outputTokens,
4406
+ total_tokens: result.usage?.totalTokens
4407
+ },
4408
+ finishReason: result.finishReason
4409
+ }
4410
+ };
4411
+ }
4412
+ async generateStreamingResponse(userMessage, context, onChunk, signal) {
4413
+ const request = this.buildRequest(userMessage, context, signal);
4414
+ const result = await this.runStream(request, { onChunk });
4415
+ return result.text;
4416
+ }
4417
+ buildRequest(userMessage, context, signal) {
4418
+ const messages = messageFormatter.toCoreMessages(context);
4419
+ messages.push({ role: "user", content: userMessage });
4420
+ return {
4421
+ messages,
4422
+ systemPrompt: this.resolveSystemPrompt(),
4423
+ model: this.config.model,
4424
+ tier: this.config.tier,
4425
+ temperature: this.config.temperature,
4426
+ maxTokens: this.config.maxTokens,
4427
+ ...signal !== void 0 ? { signal } : {}
4428
+ };
4429
+ }
4430
+ resolveSystemPrompt() {
4431
+ const prompt = this.promptStrategy.resolve(this.config);
4432
+ return prompt && prompt.length > 0 ? prompt : void 0;
4433
+ }
4434
+ async run(request) {
4435
+ if (this.engine instanceof ProviderFactory) {
4436
+ return this.engine.chat(request);
4437
+ }
4438
+ return this.engine.generate(request);
4439
+ }
4440
+ async runStream(request, callbacks) {
4441
+ if (this.engine instanceof ProviderFactory) {
4442
+ return this.engine.chatStream(request, callbacks);
4443
+ }
4444
+ return this.engine.stream(request, callbacks);
4445
+ }
4446
+ requireStorage() {
4447
+ if (!this.storage) {
4448
+ throw new AIError(
4449
+ "MISSING_CONFIG" /* MISSING_CONFIG */,
4450
+ "ChatService.getConversation requires setStorage() to be called first."
4451
+ );
4452
+ }
4453
+ return this.storage;
4454
+ }
4455
+ };
4456
+ function extractTokenUsage(message) {
4457
+ const meta = message.metadata;
4458
+ if (!meta) return null;
4459
+ const usage = meta["usage"];
4460
+ if (!usage || typeof usage !== "object") return null;
4461
+ const u = usage;
4462
+ const inputTokens = readNumber(u, ["input_tokens", "prompt_tokens"]);
4463
+ const outputTokens = readNumber(u, ["output_tokens", "completion_tokens"]);
4464
+ const totalTokens = readNumber(u, ["total_tokens"]);
4465
+ if (inputTokens === null || outputTokens === null || totalTokens === null) {
4466
+ return null;
4467
+ }
4468
+ return {
4469
+ promptTokens: inputTokens,
4470
+ completionTokens: outputTokens,
4471
+ totalTokens
4472
+ };
4473
+ }
4474
+ function readNumber(source, keys) {
4475
+ for (const key of keys) {
4476
+ const value = source[key];
4477
+ if (typeof value === "number") return value;
4478
+ }
4479
+ return null;
4480
+ }
4481
+
4482
+ // src/providers/groq/groq-container.ts
4483
+ var container = null;
4484
+ function configureProvider(config) {
4485
+ const configStore = new GroqConfigStore();
4486
+ const transport = new GroqHttpTransport(configStore);
4487
+ const httpClient = new GroqHttpClient({ configStore, transport });
4488
+ httpClient.initialize(config);
4489
+ const textGeneration = new TextGenerationService(
4490
+ httpClient,
4491
+ new CacheManager(),
4492
+ config.textModel
4493
+ );
4494
+ const chat = new ChatService(textGeneration);
4495
+ container = { groqHttpClient: httpClient, textGeneration, chat };
4496
+ return container;
4497
+ }
4498
+ function getProvider() {
4499
+ return container;
4500
+ }
4501
+ function resetProvider() {
4502
+ container?.groqHttpClient.reset();
4503
+ container = null;
4504
+ }
4505
+
4506
+ // src/content/domain/calculations/ContentCreditCost.ts
4507
+ var CREDIT_COSTS = {
4508
+ "text-to-image": { standard: 1, hd: 2, "4k": 4 },
4509
+ "image-to-image": { standard: 1, hd: 2, "4k": 4 },
4510
+ "image-to-video": { standard: 3, hd: 5, "4k": 8 },
4511
+ "text-to-video": { standard: 5, hd: 8, "4k": 12 }
4512
+ };
4513
+ var DEFAULT_CREDIT_COST = 0;
4514
+ function toQualityTier(quality) {
4515
+ if (quality === "4k") return "4k";
4516
+ if (quality === "hd" || quality === "1080p") return "hd";
4517
+ return "standard";
4518
+ }
4519
+ function calculateContentCreditCost(type, quality = "standard") {
4520
+ const tierCosts = CREDIT_COSTS[type];
4521
+ if (!tierCosts) return DEFAULT_CREDIT_COST;
4522
+ return tierCosts[toQualityTier(quality)] ?? tierCosts.standard;
4523
+ }
4524
+
4525
+ // src/content/domain/limits/ImageFormatOptions.ts
4526
+ var ASPECT_RATIO_OPTIONS = [
4527
+ { value: "1:1", label: "Square", width: 1024, height: 1024 },
4528
+ { value: "16:9", label: "Landscape", width: 1920, height: 1080 },
4529
+ { value: "9:16", label: "Portrait", width: 1080, height: 1920 },
4530
+ { value: "4:3", label: "Standard", width: 1440, height: 1080 },
4531
+ { value: "3:4", label: "Tall", width: 1080, height: 1440 }
4532
+ ];
4533
+ var IMAGE_QUALITY_OPTIONS = [
4534
+ { value: "standard", label: "Standard", description: "1024x1024, good quality", resolution: "1024x1024" },
4535
+ { value: "hd", label: "HD", description: "1920x1080, high quality", resolution: "1920x1080" },
4536
+ { value: "4k", label: "4K", description: "3840x2160, ultra quality", resolution: "3840x2160" }
4537
+ ];
4538
+ var VIDEO_QUALITY_OPTIONS = [
4539
+ { value: "720p", label: "720p HD", resolution: "1280x720" },
4540
+ { value: "1080p", label: "1080p Full HD", resolution: "1920x1080" },
4541
+ { value: "4k", label: "4K Ultra HD", resolution: "3840x2160" }
4542
+ ];
4543
+ var VIDEO_DURATION_OPTIONS_SECONDS = [2, 3, 4, 5, 6, 7, 8, 9, 10];
4544
+ var MOTION_OPTIONS = [
4545
+ { value: "slow", label: "Slow", description: "Gentle movement" },
4546
+ { value: "medium", label: "Medium", description: "Balanced motion" },
4547
+ { value: "fast", label: "Fast", description: "Dynamic movement" }
4548
+ ];
4549
+ var STYLE_PRESETS = [
4550
+ { value: "realistic", label: "Realistic", promptFragment: "realistic, photograph, detailed" },
4551
+ { value: "artistic", label: "Artistic", promptFragment: "artistic, painting, creative" },
4552
+ { value: "anime", label: "Anime", promptFragment: "anime style, manga, vibrant colors" },
4553
+ { value: "cinematic", label: "Cinematic", promptFragment: "cinematic, movie scene, dramatic lighting" },
4554
+ { value: "vintage", label: "Vintage", promptFragment: "vintage, retro, film grain" },
4555
+ { value: "fantasy", label: "Fantasy", promptFragment: "fantasy, magical, ethereal" },
4556
+ { value: "minimalist", label: "Minimalist", promptFragment: "minimalist, clean, simple" },
4557
+ { value: "abstract", label: "Abstract", promptFragment: "abstract, geometric, artistic" }
4558
+ ];
4559
+
4560
+ // src/content/domain/calculations/AspectRatioDimensions.ts
4561
+ var FALLBACK_DIMENSIONS = { width: 1024, height: 1024 };
4562
+ function resolveDimensionsForAspectRatio(ratio) {
4563
+ const match = ASPECT_RATIO_OPTIONS.find((option) => option.value === ratio);
4564
+ return match ? { width: match.width, height: match.height } : FALLBACK_DIMENSIONS;
4565
+ }
4566
+
4567
+ // src/content/domain/constants/GenerationConstants.ts
4568
+ var ASPECT_RATIO_VALUES = ASPECT_RATIO_OPTIONS.map((o) => o.value);
4569
+ var IMAGE_QUALITY_VALUES = IMAGE_QUALITY_OPTIONS.map((o) => o.value);
4570
+ var VIDEO_QUALITY_VALUES = VIDEO_QUALITY_OPTIONS.map((o) => o.value);
4571
+ var VIDEO_DURATION_OPTIONS = VIDEO_DURATION_OPTIONS_SECONDS;
4572
+ var getDimensionsForAspectRatio = resolveDimensionsForAspectRatio;
4573
+ var getRecommendedQuality = recommendQualityForAspectRatio;
4574
+
4575
+ // src/content/domain/limits/ValidationLimits.ts
4576
+ var ValidationLimits = {
4577
+ TOPIC: {
4578
+ MIN: 1,
4579
+ MAX: 500
4580
+ },
4581
+ CONTENT: {
4582
+ MIN: 1,
4583
+ MAX: 1e4
4584
+ },
4585
+ KEYWORD: {
4586
+ MAX_COUNT: 20,
4587
+ MAX_LENGTH: 50
4588
+ },
4589
+ DURATION_SECONDS: {
4590
+ MIN: 10,
4591
+ MAX: 600
4592
+ },
4593
+ CALENDAR_DAYS: {
4594
+ MIN: 1,
4595
+ MAX: 90
4596
+ },
4597
+ HASHTAG_COUNT: {
4598
+ MIN: 1,
4599
+ MAX: 50
4600
+ },
4601
+ SEO_CONTENT_PREVIEW: 500,
4602
+ BLOG_PROMPT_PREVIEW: 1e3,
4603
+ AB_VARIANT_CONTENT_PREVIEW: 200
4604
+ };
4605
+
4606
+ // src/content/domain/limits/SpeechPaceConfig.ts
4607
+ var SpeechPaceConfig = {
4608
+ WORDS_PER_SECOND: 2.5,
4609
+ TOKENS_PER_SECOND: 10,
4610
+ JSON_TOKEN_OVERHEAD: 500,
4611
+ TOKENS_PER_CALENDAR_DAY: 150
4612
+ };
4613
+
4614
+ // src/content/domain/calculations/SpeechPaceEstimate.ts
4615
+ function estimateWordCountForDuration(durationSeconds) {
4616
+ return Math.floor(durationSeconds * SpeechPaceConfig.WORDS_PER_SECOND);
4617
+ }
4618
+ function estimateTokenBudgetForDuration(durationSeconds) {
4619
+ return durationSeconds * SpeechPaceConfig.TOKENS_PER_SECOND + SpeechPaceConfig.JSON_TOKEN_OVERHEAD;
4620
+ }
4621
+ function estimateTokenBudgetForCalendarDays(days) {
4622
+ return days * SpeechPaceConfig.TOKENS_PER_CALENDAR_DAY + SpeechPaceConfig.JSON_TOKEN_OVERHEAD;
4623
+ }
4624
+
4625
+ // src/content/domain/calculations/SeoKeywordDensity.ts
4626
+ function countKeywordOccurrences(content, keyword) {
4627
+ if (!keyword) return 0;
4628
+ const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4629
+ const matches = content.match(new RegExp(escaped, "gi"));
4630
+ return matches ? matches.length : 0;
4631
+ }
4632
+ function countWordsInContent(content) {
4633
+ const tokens = content.split(/\s+/).filter(Boolean);
4634
+ return tokens.length || 1;
4635
+ }
4636
+ function calculateKeywordDensity(content, keyword) {
4637
+ const totalWords = countWordsInContent(content);
4638
+ const occurrences = countKeywordOccurrences(content, keyword);
4639
+ return occurrences / totalWords * 100;
4640
+ }
4641
+
4642
+ // src/content/domain/calculations/AbTestImprovementRatio.ts
4643
+ function calculateImprovementPercentage(scoreA, scoreB) {
4644
+ const higher = Math.max(scoreA, scoreB);
4645
+ const lower = Math.min(scoreA, scoreB);
4646
+ if (lower <= 0) {
4647
+ return "100";
4648
+ }
4649
+ const delta = (higher / lower - 1) * 100;
4650
+ return Math.abs(delta).toFixed(0);
4651
+ }
4652
+ function selectHigherScoreVariantId(scoreA, scoreB, idA = "A", idB = "B") {
4653
+ return scoreA > scoreB ? idA : idB;
4654
+ }
4655
+
4656
+ // src/content/domain/calculations/ContentCalendarDateOffset.ts
4657
+ var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
4658
+ function resolveCalendarEntryDate(dayIndex, baseTimestamp = Date.now()) {
4659
+ return new Date(baseTimestamp + (dayIndex - 1) * ONE_DAY_MS);
4660
+ }
4661
+
4662
+ // src/content/domain/validation/TopicTextValidator.ts
4663
+ function validateTopicText(topic) {
4664
+ if (typeof topic !== "string" || topic.trim().length < ValidationLimits.TOPIC.MIN) {
4665
+ throw new ValidationError("Topic cannot be empty");
4666
+ }
4667
+ const trimmed = topic.trim();
4668
+ if (trimmed.length > ValidationLimits.TOPIC.MAX) {
4669
+ throw new ValidationError(`Topic exceeds maximum length of ${ValidationLimits.TOPIC.MAX} characters`);
4670
+ }
4671
+ return trimmed;
4672
+ }
4673
+
4674
+ // src/content/domain/validation/ContentBodyValidator.ts
4675
+ function validateContentBody(content) {
4676
+ if (typeof content !== "string" || content.trim().length < ValidationLimits.CONTENT.MIN) {
4677
+ throw new ValidationError("Content cannot be empty");
4678
+ }
4679
+ const trimmed = content.trim();
4680
+ if (trimmed.length > ValidationLimits.CONTENT.MAX) {
4681
+ throw new ValidationError(`Content exceeds maximum length of ${ValidationLimits.CONTENT.MAX} characters`);
4682
+ }
4683
+ return trimmed;
4684
+ }
4685
+ function truncateForSeoPreview(content) {
4686
+ return content.substring(0, ValidationLimits.SEO_CONTENT_PREVIEW);
4687
+ }
4688
+ function truncateForBlogPromptPreview(content) {
4689
+ return content.substring(0, ValidationLimits.BLOG_PROMPT_PREVIEW);
4690
+ }
4691
+ function truncateForAbVariantPreview(content) {
4692
+ return content.substring(0, ValidationLimits.AB_VARIANT_CONTENT_PREVIEW);
4693
+ }
4694
+
4695
+ // src/content/domain/validation/KeywordListValidator.ts
4696
+ function validateKeywordList(keywords) {
4697
+ if (!Array.isArray(keywords)) {
4698
+ throw new ValidationError("Keywords must be an array");
4699
+ }
4700
+ if (keywords.length > ValidationLimits.KEYWORD.MAX_COUNT) {
4701
+ throw new ValidationError(`Cannot process more than ${ValidationLimits.KEYWORD.MAX_COUNT} keywords`);
4702
+ }
4703
+ return keywords.filter((k) => typeof k === "string" && k.trim().length > 0).map((keyword) => {
4704
+ const trimmed = keyword.trim();
4705
+ if (trimmed.length > ValidationLimits.KEYWORD.MAX_LENGTH) {
4706
+ const preview = trimmed.substring(0, 20);
4707
+ throw new ValidationError(
4708
+ `Keyword "${preview}..." exceeds maximum length of ${ValidationLimits.KEYWORD.MAX_LENGTH} characters`
4709
+ );
4710
+ }
4711
+ return trimmed;
4712
+ });
4713
+ }
4714
+
4715
+ // src/content/domain/validation/DurationSecondsValidator.ts
4716
+ function validateDurationSeconds(duration) {
4717
+ if (!Number.isFinite(duration)) {
4718
+ throw new ValidationError(
4719
+ `Duration must be between ${ValidationLimits.DURATION_SECONDS.MIN} and ${ValidationLimits.DURATION_SECONDS.MAX} seconds`
4720
+ );
4721
+ }
4722
+ if (duration < ValidationLimits.DURATION_SECONDS.MIN || duration > ValidationLimits.DURATION_SECONDS.MAX) {
4723
+ throw new ValidationError(
4724
+ `Duration must be between ${ValidationLimits.DURATION_SECONDS.MIN} and ${ValidationLimits.DURATION_SECONDS.MAX} seconds`
4725
+ );
4726
+ }
4727
+ return duration;
4728
+ }
4729
+
4730
+ // src/content/domain/validation/CalendarDaysValidator.ts
4731
+ function validateCalendarDays(days) {
4732
+ if (!Number.isFinite(days)) {
4733
+ throw new ValidationError(
4734
+ `Days must be between ${ValidationLimits.CALENDAR_DAYS.MIN} and ${ValidationLimits.CALENDAR_DAYS.MAX}`
4735
+ );
4736
+ }
4737
+ if (days < ValidationLimits.CALENDAR_DAYS.MIN || days > ValidationLimits.CALENDAR_DAYS.MAX) {
4738
+ throw new ValidationError(
4739
+ `Days must be between ${ValidationLimits.CALENDAR_DAYS.MIN} and ${ValidationLimits.CALENDAR_DAYS.MAX}`
4740
+ );
4741
+ }
4742
+ return days;
4743
+ }
4744
+
4745
+ // src/content/domain/validation/HashtagCountValidator.ts
4746
+ function validateHashtagCount(count) {
4747
+ if (!Number.isFinite(count)) {
4748
+ throw new ValidationError(
4749
+ `Hashtag count must be between ${ValidationLimits.HASHTAG_COUNT.MIN} and ${ValidationLimits.HASHTAG_COUNT.MAX}`
4750
+ );
4751
+ }
4752
+ if (count < ValidationLimits.HASHTAG_COUNT.MIN || count > ValidationLimits.HASHTAG_COUNT.MAX) {
4753
+ throw new ValidationError(
4754
+ `Hashtag count must be between ${ValidationLimits.HASHTAG_COUNT.MIN} and ${ValidationLimits.HASHTAG_COUNT.MAX}`
4755
+ );
4756
+ }
4757
+ return Math.floor(count);
4758
+ }
4759
+
4760
+ // src/content/domain/utils/IdGenerator.ts
4761
+ function generateId(prefix) {
4762
+ const id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
4763
+ return prefix ? `${prefix}-${id}` : id;
4764
+ }
4765
+
4766
+ // src/content/application/prompts/PromptBuilder.ts
4767
+ var PromptBuilder = class {
4768
+ static blogPost(input) {
4769
+ return `
4770
+ As an expert SEO Content Strategist and Copywriter, generate a high-quality, comprehensive blog post.
4771
+
4772
+ TOPIC: ${input.topic}
4773
+ TYPE: ${input.blogType}
4774
+ KEYWORDS: ${input.keywords.join(", ")}
4775
+ TONE: ${input.tone}
4776
+ TARGET AUDIENCE: ${input.targetAudience}
4777
+ WORD COUNT: ${input.wordCount}
4778
+ LANGUAGE: ${input.language || "English"}
4779
+
4780
+ OPTIMIZATION REQUIREMENTS:
4781
+ - SEO Optimization: ${input.seoOptimization ? "ON (Include semantic keywords, LSI, and proper H1-H3 structure)" : "OFF"}
4782
+ - Include Schema Markup: ${input.includeSchema ? "ON (Generate JSON-LD for this blog type)" : "OFF"}
4783
+ - Image Suggestions: ${input.includeImages ? "ON (Provide DALL-E/Midjourney style prompts for contextually relevant images)" : "OFF"}
4784
+
4785
+ RESPONSE FORMAT (STRICT JSON ONLY):
4786
+ {
4787
+ "title": "Compelling, Click-worthy Title",
4788
+ "content": "Full markdown-formatted blog content with headers",
4789
+ "metaDescription": "SEO-optimized description (150-160 chars)",
4790
+ "seoScore": 0-100,
4791
+ "readabilityScore": 0-100,
4792
+ "keywords": ["list", "of", "optimized", "keywords"],
4793
+ "performance": {
4794
+ "readingTime": minutes (number),
4795
+ "shareability": 0-10 (number),
4796
+ "seoRank": 0-10 (number)
4797
+ },
4798
+ "schemaMarkup": "JSON-LD string (if requested)",
4799
+ "imagePrompts": ["Prompt 1", "Prompt 2"] (if requested)
4800
+ }
4801
+ `;
4802
+ }
4803
+ static socialPost(input) {
4804
+ return `
4805
+ Write a ${input.tone} social media post about: ${input.topic}
4806
+
4807
+ Platform: ${input.platform}
4808
+ Style: ${input.style}
4809
+ Maximum length: ${input.maxLength} characters
4810
+ ${input.includeHashtags ? "Include relevant hashtags" : "No hashtags"}
4811
+ ${input.includeCallToAction ? "Include a call-to-action" : ""}
4812
+
4813
+ Requirements:
4814
+ - Platform-optimized formatting
4815
+ - Engaging hook in first sentence
4816
+ - Natural, conversational tone
4817
+ - Platform-appropriate emoji usage
4818
+
4819
+ Generate the post in JSON format:
4820
+ {
4821
+ "content": "The post content",
4822
+ "hashtags": ["hashtag1", "hashtag2"],
4823
+ "emojis": ["emoji1", "emoji2"],
4824
+ "estimatedEngagement": 0-100
4825
+ }
4826
+ `;
4827
+ }
4828
+ static videoScript(input) {
4829
+ return `
4830
+ Create a ${input.tone} video script about: ${input.topic}
4831
+
4832
+ Duration: ${input.duration} seconds
4833
+ Target Audience: ${input.targetAudience}
4834
+ Include Visual Cues: ${input.includeVisuals ? "Yes" : "No"}
4835
+ Include CTA: ${input.includeCallToAction ? "Yes" : "No"}
4836
+
4837
+ Approximate word count: ${estimateWordCountForDuration(input.duration)} words
4838
+
4839
+ Generate in JSON format:
4840
+ {
4841
+ "script": "Full script with dialogue and narration",
4842
+ "visualCues": ["Visual cue 1", "Visual cue 2"],
4843
+ "callToAction": "Call to action text (if requested)",
4844
+ "estimatedEngagement": 0-100
4845
+ }
4846
+ `;
4847
+ }
4848
+ static voiceScript(input) {
4849
+ return `
4850
+ Create a ${input.emotion} voice script about: ${input.topic}
4851
+
4852
+ Duration: ${input.duration} seconds
4853
+ Emotion: ${input.emotion}
4854
+
4855
+ The script should:
4856
+ - Evoke the specified emotion
4857
+ - Be suitable for voice recording with emotion control
4858
+ - Include natural speech patterns
4859
+ - Have appropriate pacing for the emotion
4860
+ - Include emotional cues in brackets where needed
4861
+
4862
+ Approximate word count: ${estimateWordCountForDuration(input.duration)} words
4863
+
4864
+ Generate in JSON format:
4865
+ {
4866
+ "script": "Full script with emotional cues",
4867
+ "visualCues": ["Visual cue 1", "Visual cue 2"],
4868
+ "estimatedEngagement": 0-100
4869
+ }
4870
+ `;
4871
+ }
4872
+ static contentCalendar(niche, days) {
4873
+ return `
4874
+ Generate a ${days}-day content calendar for: ${niche}
4875
+
4876
+ For each day, provide:
4877
+ - Topic
4878
+ - Content type (blog, video, social, email, caption, script)
4879
+ - Best platform for this content
4880
+ - Brief description
4881
+ - Estimated engagement (0-100)
4882
+ - Priority level (high, medium, low)
4883
+
4884
+ Format as JSON array:
4885
+ [
4886
+ {
4887
+ "day": 1,
4888
+ "topic": "...",
4889
+ "contentType": "blog|video|social|email|caption|script",
4890
+ "platform": "twitter|linkedin|instagram|threads|tiktok|facebook",
4891
+ "description": "...",
4892
+ "estimatedEngagement": 0-100,
4893
+ "priority": "high|medium|low"
4894
+ }
4895
+ ]
4896
+
4897
+ Requirements:
4898
+ - Mix of content types across platforms
4899
+ - Platform-appropriate suggestions
4900
+ - Engaging, varied topics
4901
+ - Realistic posting schedule
4902
+ `;
4903
+ }
4904
+ static sentiment(content) {
4905
+ return `
4906
+ Analyze the sentiment of this social media post:
4907
+
4908
+ "${content}"
4909
+
4910
+ Provide analysis in JSON format:
4911
+ {
4912
+ "sentiment": "positive|neutral|negative",
4913
+ "confidence": 0-1,
4914
+ "emotions": [
4915
+ {"emotion": "happy|excited|calm|sad|angry|surprised|fearful|disgusted|neutral", "score": 0-1}
4916
+ ]
4917
+ }
4918
+
4919
+ Return only the JSON:
4920
+ `;
4921
+ }
4922
+ static contentAnalysis(content) {
4923
+ return `
4924
+ Analyze this content:
4925
+
4926
+ "${content}"
4927
+
4928
+ Provide analysis in JSON format:
4929
+ {
4930
+ "keywords": ["keyword1", "keyword2", ...],
4931
+ "entities": ["entity1", "entity2", ...],
4932
+ "suggestedImprovements": ["suggestion1", ...],
4933
+ "readabilityScore": 0-100,
4934
+ "estimatedEngagement": 0-100
4935
+ }
4936
+
4937
+ Return only the JSON:
4938
+ `;
4939
+ }
4940
+ static seoOptimization(content, keywords) {
4941
+ return `
4942
+ Optimize this content for SEO:
4943
+
4944
+ Content: "${content}"
4945
+ Keywords: ${keywords.join(", ")}
4946
+
4947
+ Tasks:
4948
+ 1. Incorporate keywords naturally
4949
+ 2. Improve engagement potential
4950
+ 3. Add relevant call-to-action if appropriate
4951
+ 4. Optimize for readability
4952
+ 5. Enhance heading structure
4953
+
4954
+ Return JSON:
4955
+ {
4956
+ "optimized": "...",
4957
+ "score": 0-100,
4958
+ "suggestions": ["suggestion1", ...],
4959
+ "addedKeywords": ["keyword1", ...],
4960
+ "removedFillerWords": number,
4961
+ "readabilityImprovements": ["improvement1", ...],
4962
+ "metaDescription": "SEO-optimized meta description",
4963
+ "titleSuggestions": ["title1", ...]
4964
+ }
4965
+
4966
+ Return only the JSON:
4967
+ `;
4968
+ }
4969
+ static seoScore(content, keywords) {
4970
+ return `
4971
+ Calculate SEO score for this content:
4972
+
4973
+ Content: "${content}"
4974
+ Keywords: ${keywords.join(", ")}
4975
+
4976
+ Provide breakdown in JSON:
4977
+ {
4978
+ "keywordDensity": 0-100,
4979
+ "readabilityScore": 0-100,
4980
+ "titleOptimization": 0-100,
4981
+ "metaDescription": 0-100,
4982
+ "headingStructure": 0-100,
4983
+ "internalLinking": 0-100,
4984
+ "overall": 0-100
4985
+ }
4986
+
4987
+ Return only the JSON:
4988
+ `;
4989
+ }
4990
+ static abTestVariant(input) {
4991
+ return `
4992
+ Predict performance for this content variant:
4993
+
4994
+ Content: "${input.content}"
4995
+ Content Type: ${input.contentType}
4996
+ Tone: ${input.tone}
4997
+ Target Audience: ${input.targetAudience}
4998
+ Platform: ${input.platform}
4999
+ Goals: ${input.goals.join(", ")}
5000
+
5001
+ Provide prediction in JSON:
5002
+ {
5003
+ "predictedEngagement": 0-100,
5004
+ "predictedCTR": 0-100,
5005
+ "predictedConversions": 0-100,
5006
+ "confidence": 0-1,
5007
+ "reasoning": "...",
5008
+ "strengths": ["strength1", ...],
5009
+ "weaknesses": ["weakness1", ...],
5010
+ "suggestions": ["suggestion1", ...]
5011
+ }
5012
+
5013
+ Return only the JSON:
5014
+ `;
5015
+ }
5016
+ static hashtags(content, count) {
5017
+ return `
5018
+ Generate ${count} relevant hashtags for this content:
5019
+
5020
+ "${content}"
5021
+
5022
+ Requirements:
5023
+ - Mix of popular and niche hashtags
5024
+ - Industry-specific when possible
5025
+ - Trending if relevant
5026
+ - No duplicated words
5027
+ - Format: #hashtag (one per line)
5028
+
5029
+ Return only the hashtags:
5030
+ `;
5031
+ }
5032
+ static imagePrompt(description, style) {
5033
+ return `
5034
+ Generate a detailed AI image generation prompt for:
5035
+
5036
+ Description: ${description}
5037
+ Style: ${style || "realistic"}
5038
+
5039
+ Create a detailed, descriptive prompt that includes:
5040
+ - Subject details
5041
+ - Mood and atmosphere
5042
+ - Lighting
5043
+ - Composition
5044
+ - Art style reference
5045
+ - Technical specifications (aspect ratio, quality)
5046
+
5047
+ The prompt should be optimized for models like Midjourney, DALL-E, or Stable Diffusion.
5048
+
5049
+ Generate the prompt:
5050
+ `;
5051
+ }
5052
+ static blogImagePrompts(blogContent) {
5053
+ return `
5054
+ Analyze this blog content and generate 3-5 detailed image prompts for illustrative images:
5055
+
5056
+ Blog Content: "${blogContent}"
5057
+
5058
+ For each image prompt, provide:
5059
+ - Subject description
5060
+ - Mood and atmosphere
5061
+ - Style reference
5062
+ - Composition details
5063
+ - Technical specs
5064
+
5065
+ Return as JSON array:
5066
+ ["prompt1", "prompt2", "prompt3", ...]
5067
+
5068
+ Return only the JSON:
5069
+ `;
5070
+ }
5071
+ static maxTokensForDuration(durationSeconds) {
5072
+ return estimateTokenBudgetForDuration(durationSeconds);
5073
+ }
5074
+ };
5075
+
5076
+ // src/content/application/services/BlogService.ts
5077
+ var BlogService = class {
5078
+ constructor(executor) {
5079
+ this.executor = executor;
5080
+ }
5081
+ executor;
5082
+ async generateBlogPost(request) {
5083
+ const topic = validateTopicText(request.topic);
5084
+ const keywords = validateKeywordList(request.targetKeywords);
5085
+ const prompt = PromptBuilder.blogPost({
5086
+ topic,
5087
+ blogType: request.blogType,
5088
+ keywords,
5089
+ tone: request.tone,
5090
+ targetAudience: request.targetAudience,
5091
+ wordCount: request.wordCount,
5092
+ language: request.language,
5093
+ seoOptimization: request.seoOptimization,
5094
+ includeSchema: request.includeSchema,
5095
+ includeImages: request.includeImages
3204
5096
  });
5097
+ const parsed = await this.executor.runJson(
5098
+ prompt,
5099
+ {},
5100
+ { maxTokens: request.wordCount * 2, temperature: 0.7 }
5101
+ );
5102
+ return {
5103
+ id: generateId("blog"),
5104
+ ...parsed,
5105
+ blogType: request.blogType,
5106
+ tone: request.tone,
5107
+ wordCount: request.wordCount,
5108
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
5109
+ };
3205
5110
  }
3206
- streamCompletion(prompt, callbacks, options = {}) {
3207
- return this.streamChatCompletion([createUserMessage(prompt)], callbacks, options);
5111
+ };
5112
+ async function batchRequest(requests, options = {}) {
5113
+ const {
5114
+ concurrency = 3,
5115
+ stopOnError = false
5116
+ } = options;
5117
+ const results = [];
5118
+ const errors = [];
5119
+ for (let i = 0; i < requests.length; i += concurrency) {
5120
+ const batch = requests.slice(i, i + concurrency);
5121
+ const batchResults = await Promise.allSettled(
5122
+ batch.map((fn) => fn())
5123
+ );
5124
+ for (const result of batchResults) {
5125
+ if (result.status === "fulfilled") {
5126
+ results.push(result.value);
5127
+ } else {
5128
+ errors.push(result.reason);
5129
+ if (stopOnError) {
5130
+ throw new Error(
5131
+ `Batch request failed: ${errors[0].message}`
5132
+ );
5133
+ }
5134
+ }
5135
+ }
3208
5136
  }
3209
- async streamChatCompletion(messages, callbacks, options = {}) {
3210
- const model = options.model ?? this.defaultModel;
3211
- const request = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig(options.generationConfig).withStream(true).build();
3212
- const parser = new StreamingResponseParser(
3213
- {
3214
- onChunk: (chunk) => callbacks.onChunk?.(chunk)
3215
- },
3216
- extractContentFromSsePayload
5137
+ if (errors.length > 0 && !stopOnError) {
5138
+ console.warn(`${errors.length} requests failed in batch`);
5139
+ }
5140
+ return results;
5141
+ }
5142
+
5143
+ // src/content/application/services/SocialService.ts
5144
+ var MULTI_PLATFORMS = [
5145
+ "twitter",
5146
+ "linkedin",
5147
+ "instagram",
5148
+ "threads",
5149
+ "tiktok"
5150
+ ];
5151
+ var DEFAULT_SOCIAL_RESULT = {
5152
+ content: "",
5153
+ platform: "twitter",
5154
+ hashtags: [],
5155
+ emojis: [],
5156
+ characterCount: 0,
5157
+ estimatedEngagement: 0
5158
+ };
5159
+ var SocialService = class {
5160
+ constructor(executor) {
5161
+ this.executor = executor;
5162
+ }
5163
+ executor;
5164
+ async generateSocialContent(request) {
5165
+ const topic = validateTopicText(request.topic);
5166
+ const spec = resolvePlatformSpec(request.platform);
5167
+ const maxLength = resolveMaxContentLength(request.platform, request.maxLength);
5168
+ const prompt = PromptBuilder.socialPost({
5169
+ topic,
5170
+ platform: request.platform,
5171
+ tone: request.tone,
5172
+ style: spec.style,
5173
+ maxLength,
5174
+ includeHashtags: request.hashtags !== false,
5175
+ includeCallToAction: Boolean(request.includeCallToAction)
5176
+ });
5177
+ const parsed = await this.executor.runJson(
5178
+ prompt,
5179
+ { ...DEFAULT_SOCIAL_RESULT, platform: request.platform },
5180
+ { maxTokens: 500, temperature: 0.8 }
5181
+ );
5182
+ return {
5183
+ ...parsed,
5184
+ platform: request.platform,
5185
+ characterCount: parsed.content?.length ?? 0
5186
+ };
5187
+ }
5188
+ async generateForAllPlatforms(topic, tone) {
5189
+ const requests = MULTI_PLATFORMS.map(
5190
+ (platform) => () => this.generateSocialContent({ topic, platform, tone })
3217
5191
  );
3218
5192
  try {
3219
- const stream = await this.httpClient.streamChatCompletion(request, {
3220
- signal: options.signal
3221
- });
3222
- const fullText = await parser.parse(stream);
3223
- callbacks.onComplete?.(fullText);
3224
- return fullText;
5193
+ return await batchRequest(requests, { concurrency: 2, stopOnError: false });
3225
5194
  } catch (error) {
3226
- const err = error instanceof Error ? error : new Error(String(error));
3227
- callbacks.onError?.(err);
3228
- throw err;
5195
+ console.warn("Batch execution failed, falling back to sequential:", error);
5196
+ const results = [];
5197
+ for (const platform of MULTI_PLATFORMS) {
5198
+ try {
5199
+ results.push(await this.generateSocialContent({ topic, platform, tone }));
5200
+ } catch (err) {
5201
+ console.error(`Failed to generate for ${platform}:`, err);
5202
+ }
5203
+ }
5204
+ return results;
3229
5205
  }
3230
5206
  }
3231
- clearCache() {
3232
- this.cache.clear();
5207
+ async generateHashtags(content, count) {
5208
+ const validated = validateHashtagCount(count);
5209
+ const prompt = PromptBuilder.hashtags(content, validated);
5210
+ const response = await this.executor.runText(prompt, { maxTokens: 200, temperature: 0.7 });
5211
+ const hashtags = response.match(/#[\w-]+/g) || [];
5212
+ return hashtags.slice(0, validated);
3233
5213
  }
3234
- // --- ITextProvider implementation ---
3235
- resolveModel(tier) {
3236
- return GROQ_TIERS[tier] ?? this.defaultModel;
5214
+ optimizeHashtags(hashtags, platform) {
5215
+ const budget = resolveHashtagBudget(platform);
5216
+ return rankAndTrimHashtags(hashtags, budget);
3237
5217
  }
3238
- async generate(request) {
3239
- const messages = [];
3240
- if (request.systemPrompt) {
3241
- messages.push({ role: "system", content: request.systemPrompt });
3242
- }
3243
- for (const m of request.messages) {
3244
- messages.push({ role: m.role, content: m.content });
3245
- }
3246
- const model = request.model ?? (request.tier ? this.resolveModel(request.tier) : this.defaultModel);
3247
- const groqReq = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig({
3248
- temperature: request.temperature,
3249
- maxTokens: request.maxTokens
3250
- }).build();
3251
- const resp = await this.httpClient.postChatCompletion(groqReq);
3252
- const content = resp.choices[0]?.message?.content ?? "";
5218
+ };
5219
+
5220
+ // src/content/application/services/VideoScriptService.ts
5221
+ var buildScriptResult = (parsed, duration) => {
5222
+ const script = parsed.script ?? "";
5223
+ if (!script) {
5224
+ throw new Error("AI response missing script field");
5225
+ }
5226
+ const wordCount = script.trim().length === 0 ? 0 : script.trim().split(/\s+/).length;
5227
+ return {
5228
+ ...parsed,
5229
+ script,
5230
+ duration,
5231
+ wordCount
5232
+ };
5233
+ };
5234
+ var VideoScriptService = class {
5235
+ constructor(executor) {
5236
+ this.executor = executor;
5237
+ }
5238
+ executor;
5239
+ async generateVideoScript(request) {
5240
+ const topic = validateTopicText(request.topic);
5241
+ const duration = validateDurationSeconds(request.duration);
5242
+ const prompt = PromptBuilder.videoScript({
5243
+ topic,
5244
+ tone: request.tone,
5245
+ duration,
5246
+ targetAudience: request.targetAudience,
5247
+ includeVisuals: request.includeVisuals,
5248
+ includeCallToAction: request.includeCallToAction
5249
+ });
5250
+ const parsed = await this.executor.runJson(
5251
+ prompt,
5252
+ { script: "", visualCues: [], duration, wordCount: 0, estimatedEngagement: 0 },
5253
+ { maxTokens: estimateTokenBudgetForDuration(duration), temperature: 0.8 }
5254
+ );
5255
+ return buildScriptResult(parsed, duration);
5256
+ }
5257
+ async generateVoiceScript(topic, emotion, duration) {
5258
+ const validatedTopic = validateTopicText(topic);
5259
+ const validatedDuration = validateDurationSeconds(duration);
5260
+ const prompt = PromptBuilder.voiceScript({
5261
+ topic: validatedTopic,
5262
+ emotion,
5263
+ duration: validatedDuration
5264
+ });
5265
+ const parsed = await this.executor.runJson(
5266
+ prompt,
5267
+ { script: "", visualCues: [], duration: validatedDuration, wordCount: 0, estimatedEngagement: 0 },
5268
+ { maxTokens: estimateTokenBudgetForDuration(validatedDuration), temperature: 0.8 }
5269
+ );
5270
+ return buildScriptResult(parsed, validatedDuration);
5271
+ }
5272
+ };
5273
+
5274
+ // src/content/application/services/AnalysisService.ts
5275
+ var DEFAULT_SENTIMENT = {
5276
+ sentiment: "neutral",
5277
+ confidence: 0.5,
5278
+ emotions: []
5279
+ };
5280
+ var DEFAULT_ANALYSIS = {
5281
+ sentiment: DEFAULT_SENTIMENT,
5282
+ keywords: [],
5283
+ entities: [],
5284
+ suggestedImprovements: [],
5285
+ readabilityScore: 50,
5286
+ estimatedEngagement: 50
5287
+ };
5288
+ var AnalysisService = class {
5289
+ constructor(executor) {
5290
+ this.executor = executor;
5291
+ }
5292
+ executor;
5293
+ async analyzeSentiment(content) {
5294
+ const validated = validateContentBody(content);
5295
+ const prompt = PromptBuilder.sentiment(validated);
5296
+ const result = await this.executor.runJson(
5297
+ prompt,
5298
+ DEFAULT_SENTIMENT,
5299
+ { maxTokens: 200, temperature: 0.3 }
5300
+ );
3253
5301
  return {
3254
- text: content,
3255
- finishReason: resp.choices[0]?.finish_reason ?? "stop",
3256
- model: resp.model,
3257
- providerId: this.id,
3258
- usage: resp.usage ? {
3259
- inputTokens: resp.usage.prompt_tokens,
3260
- outputTokens: resp.usage.completion_tokens,
3261
- totalTokens: resp.usage.total_tokens
3262
- } : void 0
5302
+ ...result,
5303
+ confidence: clampConfidenceToValidRange(result.confidence)
3263
5304
  };
3264
5305
  }
3265
- async stream(request, callbacks) {
3266
- const messages = [];
3267
- if (request.systemPrompt) {
3268
- messages.push({ role: "system", content: request.systemPrompt });
3269
- }
3270
- for (const m of request.messages) {
3271
- messages.push({ role: m.role, content: m.content });
3272
- }
3273
- const model = request.model ?? (request.tier ? this.resolveModel(request.tier) : this.defaultModel);
3274
- const text = await this.streamChatCompletion(
3275
- messages,
5306
+ async analyzeContent(request) {
5307
+ const validated = validateContentBody(request.content);
5308
+ const sentimentResult = await this.analyzeSentiment(validated);
5309
+ const prompt = PromptBuilder.contentAnalysis(validated);
5310
+ const parsed = await this.executor.runJson(
5311
+ prompt,
5312
+ { ...DEFAULT_ANALYSIS, sentiment: sentimentResult },
5313
+ { maxTokens: 300, temperature: 0.5 }
5314
+ );
5315
+ return {
5316
+ sentiment: sentimentResult,
5317
+ keywords: parsed.keywords ?? [],
5318
+ entities: parsed.entities ?? [],
5319
+ suggestedImprovements: parsed.suggestedImprovements ?? [],
5320
+ readabilityScore: clampScoreToValidRange(parsed.readabilityScore),
5321
+ estimatedEngagement: clampScoreToValidRange(parsed.estimatedEngagement)
5322
+ };
5323
+ }
5324
+ };
5325
+
5326
+ // src/content/application/services/SeoService.ts
5327
+ var DEFAULT_SEO_SCORE = {
5328
+ keywordDensity: 50,
5329
+ readabilityScore: 50,
5330
+ titleOptimization: 50,
5331
+ metaDescription: 50,
5332
+ headingStructure: 50,
5333
+ internalLinking: 50,
5334
+ overall: 50
5335
+ };
5336
+ var UNDERUTILIZED_SUGGESTION = "Consider using this keyword more frequently";
5337
+ var SeoService = class {
5338
+ constructor(executor) {
5339
+ this.executor = executor;
5340
+ }
5341
+ executor;
5342
+ async optimizeSEO(request) {
5343
+ const content = validateContentBody(request.content);
5344
+ const keywords = validateKeywordList(request.keywords);
5345
+ const prompt = PromptBuilder.seoOptimization(content, keywords);
5346
+ const result = await this.executor.runJson(
5347
+ prompt,
3276
5348
  {
3277
- onChunk: callbacks.onChunk,
3278
- onError: callbacks.onError
5349
+ optimized: content,
5350
+ score: 50,
5351
+ suggestions: [],
5352
+ addedKeywords: [],
5353
+ removedFillerWords: 0,
5354
+ readabilityImprovements: []
3279
5355
  },
3280
- {
3281
- model,
3282
- signal: request.signal,
3283
- generationConfig: {
3284
- temperature: request.temperature,
3285
- maxTokens: request.maxTokens
3286
- }
3287
- }
5356
+ { maxTokens: 1e3, temperature: 0.7 }
3288
5357
  );
3289
- const result = {
3290
- text,
3291
- finishReason: "stop",
3292
- model,
3293
- providerId: this.id
5358
+ return {
5359
+ ...result,
5360
+ score: clampScoreToValidRange(result.score)
3294
5361
  };
3295
- callbacks.onComplete?.(result);
3296
- return result;
3297
5362
  }
3298
- async countTokens(text) {
3299
- return Math.max(1, Math.ceil(text.length / 4));
5363
+ async calculateSEOScore(content, keywords) {
5364
+ const validatedContent = validateContentBody(content);
5365
+ const validatedKeywords = validateKeywordList(keywords);
5366
+ const prompt = PromptBuilder.seoOptimization(
5367
+ truncateForSeoPreview(validatedContent),
5368
+ validatedKeywords
5369
+ );
5370
+ const result = await this.executor.runJson(
5371
+ prompt,
5372
+ DEFAULT_SEO_SCORE,
5373
+ { maxTokens: 200, temperature: 0.3 }
5374
+ );
5375
+ return {
5376
+ keywordDensity: clampScoreToValidRange(result.keywordDensity),
5377
+ readabilityScore: clampScoreToValidRange(result.readabilityScore),
5378
+ titleOptimization: clampScoreToValidRange(result.titleOptimization),
5379
+ metaDescription: clampScoreToValidRange(result.metaDescription),
5380
+ headingStructure: clampScoreToValidRange(result.headingStructure),
5381
+ internalLinking: clampScoreToValidRange(result.internalLinking),
5382
+ overall: clampScoreToValidRange(result.overall)
5383
+ };
3300
5384
  }
3301
- async estimateCost(_request) {
3302
- return 0;
5385
+ analyzeKeywords(content, keywords) {
5386
+ const validatedContent = validateContentBody(content);
5387
+ const validatedKeywords = validateKeywordList(keywords);
5388
+ return validatedKeywords.map((keyword) => {
5389
+ const count = countKeywordOccurrences(validatedContent, keyword);
5390
+ const density = calculateKeywordDensity(validatedContent, keyword);
5391
+ return {
5392
+ keyword,
5393
+ count,
5394
+ density,
5395
+ prominence: classifyKeywordProminence(density),
5396
+ suggestions: isUnderutilizedKeyword(density) ? [UNDERUTILIZED_SUGGESTION] : []
5397
+ };
5398
+ });
3303
5399
  }
3304
- async healthCheck() {
3305
- try {
3306
- await this.generateCompletion("ping", {
3307
- generationConfig: { maxTokens: 1 }
5400
+ };
5401
+
5402
+ // src/content/application/services/ABTestService.ts
5403
+ var DEFAULT_PREDICTION = {
5404
+ variantId: "",
5405
+ predictedEngagement: 0,
5406
+ predictedCTR: 0,
5407
+ predictedConversions: 0,
5408
+ confidence: 0,
5409
+ reasoning: "",
5410
+ strengths: [],
5411
+ weaknesses: [],
5412
+ suggestions: []
5413
+ };
5414
+ var VARIANT_A_ID = "A";
5415
+ var VARIANT_B_ID = "B";
5416
+ var FALLBACK_ENGAGEMENT = 50;
5417
+ var DEFAULT_RECOMMENDATIONS = [
5418
+ "Test with larger audience",
5419
+ "Monitor click-through rates"
5420
+ ];
5421
+ var ABTestService = class {
5422
+ constructor(executor) {
5423
+ this.executor = executor;
5424
+ }
5425
+ executor;
5426
+ async predictABTest(request) {
5427
+ const predictions = [];
5428
+ for (const variant of request.variants) {
5429
+ const prompt = PromptBuilder.abTestVariant({
5430
+ content: truncateForAbVariantPreview(variant.content),
5431
+ contentType: variant.contentType,
5432
+ tone: variant.tone,
5433
+ targetAudience: request.targetAudience,
5434
+ platform: request.platform,
5435
+ goals: request.goals
5436
+ });
5437
+ const parsed = await this.executor.runJson(
5438
+ prompt,
5439
+ { ...DEFAULT_PREDICTION, variantId: variant.id },
5440
+ { maxTokens: 300, temperature: 0.5 }
5441
+ );
5442
+ predictions.push({
5443
+ variantId: variant.id,
5444
+ predictedEngagement: clampScoreToValidRange(parsed.predictedEngagement),
5445
+ predictedCTR: clampScoreToValidRange(parsed.predictedCTR),
5446
+ predictedConversions: clampScoreToValidRange(parsed.predictedConversions),
5447
+ confidence: clampConfidenceToValidRange(parsed.confidence),
5448
+ reasoning: parsed.reasoning ?? "",
5449
+ strengths: parsed.strengths ?? [],
5450
+ weaknesses: parsed.weaknesses ?? [],
5451
+ suggestions: parsed.suggestions ?? []
3308
5452
  });
3309
- return "healthy";
3310
- } catch {
3311
- return "unavailable";
3312
5453
  }
5454
+ return predictions;
5455
+ }
5456
+ async compareVariants(variantA, variantB) {
5457
+ const predictions = await this.predictABTest({
5458
+ variants: [
5459
+ { id: VARIANT_A_ID, content: variantA, contentType: "social", tone: "casual" },
5460
+ { id: VARIANT_B_ID, content: variantB, contentType: "social", tone: "casual" }
5461
+ ],
5462
+ targetAudience: "general",
5463
+ platform: "twitter",
5464
+ goals: ["engagement", "clicks"]
5465
+ });
5466
+ const scoreA = predictions[0]?.predictedEngagement ?? FALLBACK_ENGAGEMENT;
5467
+ const scoreB = predictions[1]?.predictedEngagement ?? FALLBACK_ENGAGEMENT;
5468
+ const isTie = scoreA === scoreB;
5469
+ const winner = selectHigherScoreVariantId(scoreA, scoreB, VARIANT_A_ID, VARIANT_B_ID);
5470
+ const improvement = calculateImprovementPercentage(scoreA, scoreB);
5471
+ return {
5472
+ winner,
5473
+ confidence: AB_TEST_DEFAULT_CONFIDENCE,
5474
+ improvement: isTie ? "0%" : `+${improvement}% engagement`,
5475
+ reasoning: isTie ? "Both variants have equal predicted engagement" : winner === VARIANT_A_ID ? "Variant A has more engaging content" : "Variant B has more engaging content",
5476
+ recommendations: [...DEFAULT_RECOMMENDATIONS]
5477
+ };
3313
5478
  }
3314
- async executeWithCache(params, factory) {
3315
- const key = this.cache.generateKey(params);
3316
- const cached = this.cache.get(key);
3317
- if (cached !== null) {
3318
- return cached;
3319
- }
3320
- const result = await factory();
3321
- this.cache.set(key, result);
3322
- return result;
5479
+ };
5480
+
5481
+ // src/content/application/services/ImagePromptService.ts
5482
+ var ImagePromptService = class {
5483
+ constructor(executor) {
5484
+ this.executor = executor;
3323
5485
  }
3324
- extractContent(content) {
3325
- if (!content) {
3326
- throw new GroqError(
3327
- "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
3328
- "No content generated from Groq API"
3329
- );
3330
- }
3331
- return content;
5486
+ executor;
5487
+ async generateImagePrompt(description, style) {
5488
+ const validated = validateTopicText(description);
5489
+ const prompt = PromptBuilder.imagePrompt(validated, style);
5490
+ return this.executor.runText(prompt, { maxTokens: 300, temperature: 0.8 });
5491
+ }
5492
+ async generateImagePromptsForBlog(blogContent) {
5493
+ const truncated = truncateForBlogPromptPreview(validateContentBody(blogContent));
5494
+ const prompt = PromptBuilder.blogImagePrompts(truncated);
5495
+ const prompts = await this.executor.runJson(prompt, [], {
5496
+ maxTokens: 500,
5497
+ temperature: 0.8
5498
+ });
5499
+ return Array.isArray(prompts) ? prompts : [];
3332
5500
  }
3333
5501
  };
3334
5502
 
3335
- // src/core/calculations/random-base36-suffix.ts
3336
- var BASE_36_RADIX = 36;
3337
- var DECIMAL_OFFSET = 2;
3338
- function generateRandomBase36Suffix(length, random = Math.random) {
3339
- if (length <= 0) return "";
3340
- const raw = random().toString(BASE_36_RADIX).slice(DECIMAL_OFFSET);
3341
- if (raw.length >= length) return raw.slice(0, length);
3342
- let result = raw;
3343
- while (result.length < length) {
3344
- result += random().toString(BASE_36_RADIX).slice(DECIMAL_OFFSET);
5503
+ // src/content/application/services/ContentCalendarService.ts
5504
+ var toCalendarEntry = (item) => ({
5505
+ date: resolveCalendarEntryDate(item.day),
5506
+ topic: item.topic,
5507
+ contentType: item.contentType,
5508
+ platform: item.platform,
5509
+ description: item.description,
5510
+ estimatedEngagement: item.estimatedEngagement,
5511
+ priority: item.priority
5512
+ });
5513
+ var ContentCalendarService = class {
5514
+ constructor(executor) {
5515
+ this.executor = executor;
3345
5516
  }
3346
- return result.slice(0, length);
3347
- }
5517
+ executor;
5518
+ async generateContentCalendar(niche, days) {
5519
+ const validatedNiche = validateTopicText(niche);
5520
+ const validatedDays = validateCalendarDays(days);
5521
+ const prompt = PromptBuilder.contentCalendar(validatedNiche, validatedDays);
5522
+ const data = await this.executor.runJson(
5523
+ prompt,
5524
+ [],
5525
+ {
5526
+ maxTokens: estimateTokenBudgetForCalendarDays(validatedDays),
5527
+ temperature: 0.8
5528
+ }
5529
+ );
5530
+ const items = Array.isArray(data) ? data : Array.isArray(data?.calendar) ? data.calendar : Array.isArray(data?.entries) ? data.entries : [];
5531
+ return items.map(toCalendarEntry);
5532
+ }
5533
+ };
3348
5534
 
3349
- // src/chat/utils/chat-id-generator.ts
3350
- var ID_PART_DELIMITER = "-";
3351
- var ChatIdGenerator = class {
3352
- counter = 0;
3353
- randomLength;
3354
- constructor(randomLength = CHAT_MESSAGE_ID_SUFFIX_LENGTH) {
3355
- this.randomLength = randomLength;
5535
+ // src/content/application/services/ProviderServices.ts
5536
+ var wrapProviderError = (operation, error) => {
5537
+ const message = error instanceof Error ? error.message : "Unknown error";
5538
+ return new AIError(`${operation} failed: ${message}`, "PROVIDER_ERROR", error);
5539
+ };
5540
+ var ensureFactory = (factory, operation) => {
5541
+ if (factory.getAllProviders().length === 0) {
5542
+ throw new AIError(
5543
+ `${operation} failed: No providers are registered. Provide a ProviderConfig with at least one enabled provider when constructing AIContentService.`,
5544
+ "PROVIDER_UNAVAILABLE"
5545
+ );
3356
5546
  }
3357
- next(sender) {
3358
- this.counter += 1;
3359
- const random = generateRandomBase36Suffix(this.randomLength);
3360
- return [
3361
- MESSAGE_ID_PREFIX,
3362
- Date.now(),
3363
- sender,
3364
- this.counter,
3365
- random
3366
- ].join(ID_PART_DELIMITER);
5547
+ };
5548
+ var MediaGenerationService = class {
5549
+ constructor(providerFactory) {
5550
+ this.providerFactory = providerFactory;
5551
+ }
5552
+ providerFactory;
5553
+ generateImage(request) {
5554
+ ensureFactory(this.providerFactory, "Image generation");
5555
+ return this.providerFactory.generateImage(request).catch((err) => {
5556
+ throw wrapProviderError("Image generation", err);
5557
+ });
5558
+ }
5559
+ generateVideo(request) {
5560
+ ensureFactory(this.providerFactory, "Video generation");
5561
+ return this.providerFactory.generateVideo(request).catch((err) => {
5562
+ throw wrapProviderError("Video generation", err);
5563
+ });
5564
+ }
5565
+ convertImageToVideo(request) {
5566
+ ensureFactory(this.providerFactory, "Image-to-video conversion");
5567
+ return this.providerFactory.imageToVideo(request).catch((err) => {
5568
+ throw wrapProviderError("Image-to-video conversion", err);
5569
+ });
5570
+ }
5571
+ transformVideo(request) {
5572
+ ensureFactory(this.providerFactory, "Video-to-video transformation");
5573
+ return this.providerFactory.videoToVideo(request).catch((err) => {
5574
+ throw wrapProviderError("Video-to-video transformation", err);
5575
+ });
3367
5576
  }
3368
5577
  };
3369
- var chatIdGenerator = new ChatIdGenerator();
3370
5578
 
3371
- // src/chat/utils/message-formatter.ts
3372
- var MessageFormatter = class {
3373
- constructor(idGenerator = chatIdGenerator) {
3374
- this.idGenerator = idGenerator;
5579
+ // src/content/infrastructure/utils/jsonParser.ts
5580
+ function repairCommonJsonDefects2(text) {
5581
+ return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
5582
+ }
5583
+ function safeJSONParse(text, fallback, onError) {
5584
+ try {
5585
+ return JSON.parse(text);
5586
+ } catch (error) {
5587
+ try {
5588
+ return JSON.parse(repairCommonJsonDefects2(text));
5589
+ } catch {
5590
+ }
5591
+ if (error instanceof SyntaxError) {
5592
+ const positionMatch = error.message.match(/position (\d+)/);
5593
+ const position = positionMatch ? parseInt(positionMatch[1], 10) : -1;
5594
+ if (position > 0) {
5595
+ text.substring(Math.max(0, position - 50), position + 50);
5596
+ }
5597
+ }
5598
+ return fallback;
3375
5599
  }
3376
- idGenerator;
3377
- /** Maps chat-domain history to neutral core messages; system senders are
3378
- * dropped — the system prompt travels on the request's own slot. */
3379
- toCoreMessages(messages) {
3380
- const coreMessages = [];
3381
- for (const message of messages) {
3382
- if (message.sender === "system") continue;
3383
- coreMessages.push({
3384
- role: message.sender === "user" ? USER_ROLE : ASSISTANT_ROLE,
3385
- content: message.content
3386
- });
5600
+ }
5601
+ function extractJSON(text) {
5602
+ const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
5603
+ if (codeBlockMatch && codeBlockMatch[1]) {
5604
+ const candidate = codeBlockMatch[1].trim();
5605
+ if (candidate.startsWith("{") || candidate.startsWith("[")) {
5606
+ return candidate;
3387
5607
  }
3388
- return coreMessages;
3389
5608
  }
3390
- toChatMessage(content, sender) {
3391
- return {
3392
- id: this.idGenerator.next(sender),
3393
- sender,
3394
- content: content.trim(),
3395
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3396
- type: "text"
3397
- };
5609
+ const objectMatch = text.match(/\{[\s\S]*\}/);
5610
+ if (objectMatch) {
5611
+ return objectMatch[0];
3398
5612
  }
3399
- };
3400
- var messageFormatter = new MessageFormatter();
5613
+ const arrayMatch = text.match(/\[[\s\S]*\]/);
5614
+ if (arrayMatch) {
5615
+ return arrayMatch[0];
5616
+ }
5617
+ return null;
5618
+ }
5619
+ function parseAIResponse(response, fallback, onError) {
5620
+ const json = extractJSON(response);
5621
+ if (!json) {
5622
+ return fallback;
5623
+ }
5624
+ return safeJSONParse(json, fallback);
5625
+ }
3401
5626
 
3402
- // src/chat/utils/prompt-strategy.ts
3403
- var DEFAULT_PROMPT_STRATEGY = {
3404
- resolve: (config) => {
3405
- if (config.systemPrompt && config.systemPrompt.length > 0) {
3406
- return config.systemPrompt;
5627
+ // src/content/application/services/GenerationExecutor.ts
5628
+ var GenerationExecutor = class {
5629
+ constructor(generator) {
5630
+ this.generator = generator;
5631
+ }
5632
+ generator;
5633
+ async runJson(prompt, fallback, generationOptions = {}) {
5634
+ const response = await this.generator.generateText(prompt, generationOptions);
5635
+ return parseAIResponse(response, fallback);
5636
+ }
5637
+ runText(prompt, options = {}) {
5638
+ return this.generator.generateText(prompt, options);
5639
+ }
5640
+ /** Usage-complete pass-through; requires a ProviderFactory engine. */
5641
+ runChat(request) {
5642
+ if (!(this.generator instanceof ProviderFactory)) {
5643
+ throw new AIError(
5644
+ "runChat requires a ProviderFactory engine \u2014 construct AIContentService with a provider config, not a custom ITextGenerator",
5645
+ "PROVIDER_UNAVAILABLE"
5646
+ );
3407
5647
  }
3408
- return void 0;
5648
+ return this.generator.chat(request);
3409
5649
  }
3410
5650
  };
3411
5651
 
3412
- // src/chat/services/chat.service.ts
3413
- var DEFAULT_CONFIG = {
3414
- temperature: 0.8,
3415
- maxTokens: 500
5652
+ // src/content/application/services/AIContentService.ts
5653
+ var isTextGenerator = (value) => typeof value === "object" && value !== null && typeof value.generateText === "function";
5654
+ function toAIProviderConfig(config) {
5655
+ const legacy = config;
5656
+ const modern = config;
5657
+ const anthropic = modern.anthropic ?? (legacy.anthropic?.enabled ? {
5658
+ apiKey: legacy.anthropic.apiKey,
5659
+ baseUrl: legacy.anthropic.baseUrl,
5660
+ timeoutMs: legacy.anthropic.timeout,
5661
+ models: legacy.anthropic.models,
5662
+ effort: legacy.anthropic.effort
5663
+ } : void 0);
5664
+ const groq = modern.groq ?? (legacy.groq?.enabled ? {
5665
+ apiKey: legacy.groq.apiKey,
5666
+ baseUrl: legacy.groq.baseUrl,
5667
+ timeoutMs: legacy.groq.timeout,
5668
+ // legacy {text, chat} model pins map onto the neutral tier slots
5669
+ ...legacy.groq.models?.text ? { models: { balanced: legacy.groq.models.text } } : {}
5670
+ } : void 0);
5671
+ const gemini = modern.gemini ?? (legacy.gemini?.enabled ? { ...legacy.gemini } : void 0);
5672
+ const fal = modern.fal ?? (legacy.fal?.enabled ? { ...legacy.fal } : void 0);
5673
+ const pruna = modern.pruna ?? (legacy.pruna?.enabled ? { ...legacy.pruna } : void 0);
5674
+ return {
5675
+ priority: modern.priority,
5676
+ fallbackEnabled: modern.fallbackEnabled,
5677
+ defaultTier: modern.defaultTier,
5678
+ ...anthropic ? { anthropic } : {},
5679
+ ...groq ? { groq } : {},
5680
+ ...gemini ? { gemini } : {},
5681
+ ...fal ? { fal } : {},
5682
+ ...pruna ? { pruna } : {}
5683
+ };
5684
+ }
5685
+ var buildServices = (configOrGenerator) => {
5686
+ if (isTextGenerator(configOrGenerator)) {
5687
+ const executor2 = new GenerationExecutor(configOrGenerator);
5688
+ const emptyFactory = new ProviderFactory({
5689
+ priority: [],
5690
+ fallbackEnabled: false
5691
+ });
5692
+ return {
5693
+ providerFactory: emptyFactory,
5694
+ blogService: new BlogService(executor2),
5695
+ socialService: new SocialService(executor2),
5696
+ videoScriptService: new VideoScriptService(executor2),
5697
+ analysisService: new AnalysisService(executor2),
5698
+ seoService: new SeoService(executor2),
5699
+ abTestService: new ABTestService(executor2),
5700
+ imagePromptService: new ImagePromptService(executor2),
5701
+ contentCalendarService: new ContentCalendarService(executor2),
5702
+ mediaGenerationService: new MediaGenerationService(emptyFactory)
5703
+ };
5704
+ }
5705
+ const factory = createAI(toAIProviderConfig(configOrGenerator));
5706
+ const executor = new GenerationExecutor(factory);
5707
+ return {
5708
+ providerFactory: factory,
5709
+ blogService: new BlogService(executor),
5710
+ socialService: new SocialService(executor),
5711
+ videoScriptService: new VideoScriptService(executor),
5712
+ analysisService: new AnalysisService(executor),
5713
+ seoService: new SeoService(executor),
5714
+ abTestService: new ABTestService(executor),
5715
+ imagePromptService: new ImagePromptService(executor),
5716
+ contentCalendarService: new ContentCalendarService(executor),
5717
+ mediaGenerationService: new MediaGenerationService(factory)
5718
+ };
3416
5719
  };
3417
- var ChatService = class {
3418
- constructor(engine) {
3419
- this.engine = engine;
5720
+ var AIContentService = class {
5721
+ providerFactory;
5722
+ blogService;
5723
+ socialService;
5724
+ videoScriptService;
5725
+ analysisService;
5726
+ seoService;
5727
+ abTestService;
5728
+ imagePromptService;
5729
+ contentCalendarService;
5730
+ mediaGenerationService;
5731
+ constructor(configOrGenerator) {
5732
+ const bundle = buildServices(configOrGenerator);
5733
+ this.providerFactory = bundle.providerFactory;
5734
+ this.blogService = bundle.blogService;
5735
+ this.socialService = bundle.socialService;
5736
+ this.videoScriptService = bundle.videoScriptService;
5737
+ this.analysisService = bundle.analysisService;
5738
+ this.seoService = bundle.seoService;
5739
+ this.abTestService = bundle.abTestService;
5740
+ this.imagePromptService = bundle.imagePromptService;
5741
+ this.contentCalendarService = bundle.contentCalendarService;
5742
+ this.mediaGenerationService = bundle.mediaGenerationService;
3420
5743
  }
3421
- engine;
3422
- config = DEFAULT_CONFIG;
3423
- promptStrategy = DEFAULT_PROMPT_STRATEGY;
3424
- storage = null;
3425
- initialize(config) {
3426
- this.config = { ...DEFAULT_CONFIG, ...config };
5744
+ /** The composed factory — chat()/chatStream() with usage, health, fallback. */
5745
+ get providers() {
5746
+ return this.providerFactory;
3427
5747
  }
3428
- updateConfig(config) {
3429
- this.config = { ...this.config, ...config };
5748
+ generateBlogPost(request) {
5749
+ return this.blogService.generateBlogPost(request);
3430
5750
  }
3431
- setPromptStrategy(strategy) {
3432
- this.promptStrategy = strategy;
5751
+ generateSocialContent(request) {
5752
+ return this.socialService.generateSocialContent(request);
3433
5753
  }
3434
- setStorage(storage) {
3435
- this.storage = storage;
5754
+ generateForAllPlatforms(topic, tone) {
5755
+ return this.socialService.generateForAllPlatforms(topic, tone);
3436
5756
  }
3437
- async getConversation(conversationId) {
3438
- const storage = this.requireStorage();
3439
- const messages = await storage.getMessages(conversationId);
3440
- if (messages.length === 0) return null;
3441
- const last = messages[messages.length - 1];
3442
- const first = messages[0];
3443
- const companionId = String(first.metadata?.["companionId"] ?? "unknown");
3444
- const companionName = String(first.metadata?.["companionName"] ?? "Unknown");
3445
- return {
3446
- id: conversationId,
3447
- companionId,
3448
- companionName,
3449
- lastMessage: last,
3450
- messageCount: messages.length,
3451
- updatedAt: last.timestamp
3452
- };
5757
+ generateHashtags(content, count) {
5758
+ return this.socialService.generateHashtags(content, count);
3453
5759
  }
3454
- async getConversations() {
3455
- if (this.storage && "getConversations" in this.storage && typeof this.storage.getConversations === "function") {
3456
- return this.storage.getConversations();
3457
- }
3458
- return [];
5760
+ optimizeHashtags(hashtags, platform) {
5761
+ return Promise.resolve(this.socialService.optimizeHashtags(hashtags, platform));
3459
5762
  }
3460
- async sendMessage(input, signal) {
3461
- const userMessage = {
3462
- ...messageFormatter.toChatMessage(input.text, "user"),
3463
- type: input.type ?? "text",
3464
- ...input.imageUrl !== void 0 ? { imageUrl: input.imageUrl } : {}
3465
- };
3466
- const aiResponse = await this.generateAIResponse(
3467
- input.companionId,
3468
- input.text,
3469
- [],
3470
- signal
3471
- );
3472
- return { userMessage, aiResponse };
5763
+ generateVideoScript(request) {
5764
+ return this.videoScriptService.generateVideoScript(request);
3473
5765
  }
3474
- async generateAIResponse(companionId, userMessage, context = [], signal) {
3475
- const result = await this.run({
3476
- ...this.buildRequest(userMessage, context, signal)
3477
- });
3478
- return {
3479
- ...messageFormatter.toChatMessage(result.text, "assistant"),
3480
- metadata: {
3481
- companionId,
3482
- model: result.model,
3483
- usage: {
3484
- input_tokens: result.usage?.inputTokens,
3485
- output_tokens: result.usage?.outputTokens,
3486
- total_tokens: result.usage?.totalTokens
3487
- },
3488
- finishReason: result.finishReason
3489
- }
3490
- };
5766
+ generateVoiceScript(topic, emotion, duration) {
5767
+ return this.videoScriptService.generateVoiceScript(topic, emotion, duration);
3491
5768
  }
3492
- async generateStreamingResponse(userMessage, context, onChunk, signal) {
3493
- const request = this.buildRequest(userMessage, context, signal);
3494
- const result = await this.runStream(request, { onChunk });
3495
- return result.text;
5769
+ generateContentCalendar(niche, days) {
5770
+ return this.contentCalendarService.generateContentCalendar(niche, days);
3496
5771
  }
3497
- buildRequest(userMessage, context, signal) {
3498
- const messages = messageFormatter.toCoreMessages(context);
3499
- messages.push({ role: "user", content: userMessage });
3500
- return {
3501
- messages,
3502
- systemPrompt: this.resolveSystemPrompt(),
3503
- model: this.config.model,
3504
- tier: this.config.tier,
3505
- temperature: this.config.temperature,
3506
- maxTokens: this.config.maxTokens,
3507
- ...signal !== void 0 ? { signal } : {}
3508
- };
5772
+ analyzeSentiment(content) {
5773
+ return this.analysisService.analyzeSentiment(content);
3509
5774
  }
3510
- resolveSystemPrompt() {
3511
- const prompt = this.promptStrategy.resolve(this.config);
3512
- return prompt && prompt.length > 0 ? prompt : void 0;
5775
+ analyzeContent(request) {
5776
+ return this.analysisService.analyzeContent(request);
3513
5777
  }
3514
- async run(request) {
3515
- if (this.engine instanceof ProviderFactory) {
3516
- return this.engine.chat(request);
3517
- }
3518
- return this.engine.generate(request);
5778
+ optimizeSEO(request) {
5779
+ return this.seoService.optimizeSEO(request);
3519
5780
  }
3520
- async runStream(request, callbacks) {
3521
- if (this.engine instanceof ProviderFactory) {
3522
- return this.engine.chatStream(request, callbacks);
3523
- }
3524
- return this.engine.stream(request, callbacks);
5781
+ calculateSEOScore(content, keywords) {
5782
+ return this.seoService.calculateSEOScore(content, keywords);
3525
5783
  }
3526
- requireStorage() {
3527
- if (!this.storage) {
3528
- throw new AIError(
3529
- "MISSING_CONFIG" /* MISSING_CONFIG */,
3530
- "ChatService.getConversation requires setStorage() to be called first."
3531
- );
3532
- }
3533
- return this.storage;
5784
+ analyzeKeywords(content, keywords) {
5785
+ return Promise.resolve(this.seoService.analyzeKeywords(content, keywords));
5786
+ }
5787
+ predictABTest(request) {
5788
+ return this.abTestService.predictABTest(request);
5789
+ }
5790
+ compareVariants(variantA, variantB) {
5791
+ return this.abTestService.compareVariants(variantA, variantB);
5792
+ }
5793
+ generateImagePrompt(description, style) {
5794
+ return this.imagePromptService.generateImagePrompt(description, style);
5795
+ }
5796
+ generateImagePromptsForBlog(blogContent) {
5797
+ return this.imagePromptService.generateImagePromptsForBlog(blogContent);
5798
+ }
5799
+ generateImage(request) {
5800
+ return this.mediaGenerationService.generateImage(request);
5801
+ }
5802
+ generateVideo(request) {
5803
+ return this.mediaGenerationService.generateVideo(request);
5804
+ }
5805
+ convertImageToVideo(request) {
5806
+ return this.mediaGenerationService.convertImageToVideo(request);
5807
+ }
5808
+ transformVideo(request) {
5809
+ return this.mediaGenerationService.transformVideo(request);
3534
5810
  }
3535
5811
  };
3536
- function extractTokenUsage(message) {
3537
- const meta = message.metadata;
3538
- if (!meta) return null;
3539
- const usage = meta["usage"];
3540
- if (!usage || typeof usage !== "object") return null;
3541
- const u = usage;
3542
- const inputTokens = readNumber(u, ["input_tokens", "prompt_tokens"]);
3543
- const outputTokens = readNumber(u, ["output_tokens", "completion_tokens"]);
3544
- const totalTokens = readNumber(u, ["total_tokens"]);
3545
- if (inputTokens === null || outputTokens === null || totalTokens === null) {
3546
- return null;
5812
+
5813
+ // src/wizard/content.wizard.ts
5814
+ var CONTENT_WIZARD_STEPS = [
5815
+ {
5816
+ id: "select-type",
5817
+ label: "Select Type",
5818
+ icon: "Layers",
5819
+ description: "Choose the type of content you want to generate",
5820
+ validate: (data) => Boolean(data.contentType)
5821
+ },
5822
+ {
5823
+ id: "input-topic",
5824
+ label: "Topic",
5825
+ icon: "Lightbulb",
5826
+ description: "Enter your topic or idea",
5827
+ validate: (data) => {
5828
+ const topic = data.topic;
5829
+ return typeof topic === "string" && topic.length > 0;
5830
+ }
5831
+ },
5832
+ {
5833
+ id: "configure",
5834
+ label: "Configure",
5835
+ icon: "Settings",
5836
+ description: "Set tone, audience, and platform",
5837
+ validate: (data) => {
5838
+ const d = data;
5839
+ return Boolean(d.tone && d.targetAudience);
5840
+ }
5841
+ },
5842
+ {
5843
+ id: "advanced",
5844
+ label: "Advanced",
5845
+ icon: "Sliders",
5846
+ description: "Additional options and optimizations",
5847
+ optional: true
5848
+ },
5849
+ {
5850
+ id: "preview",
5851
+ label: "Preview",
5852
+ icon: "Eye",
5853
+ description: "Review your settings before generation"
5854
+ },
5855
+ {
5856
+ id: "generating",
5857
+ label: "Generating",
5858
+ icon: "Sparkles",
5859
+ description: "AI is generating your content"
5860
+ },
5861
+ {
5862
+ id: "results",
5863
+ label: "Results",
5864
+ icon: "CheckCircle",
5865
+ description: "Your generated content is ready"
3547
5866
  }
3548
- return {
3549
- promptTokens: inputTokens,
3550
- completionTokens: outputTokens,
3551
- totalTokens
3552
- };
3553
- }
3554
- function readNumber(source, keys) {
3555
- for (const key of keys) {
3556
- const value = source[key];
3557
- if (typeof value === "number") return value;
5867
+ ];
5868
+ function buildContentRequest(data) {
5869
+ switch (data.contentType) {
5870
+ case "blog":
5871
+ return {
5872
+ topic: data.topic ?? "",
5873
+ blogType: "tutorial",
5874
+ targetKeywords: data.keywords ?? [],
5875
+ tone: data.tone ?? "professional",
5876
+ wordCount: data.wordCount ?? 1e3,
5877
+ targetAudience: data.targetAudience ?? "",
5878
+ seoOptimization: data.seoOptimization ?? false,
5879
+ includeImages: data.includeImages ?? false,
5880
+ includeSchema: false
5881
+ };
5882
+ case "social":
5883
+ return {
5884
+ topic: data.topic ?? "",
5885
+ platform: data.platform ?? "twitter",
5886
+ tone: data.tone ?? "casual",
5887
+ hashtags: true,
5888
+ targetAudience: data.targetAudience,
5889
+ includeCallToAction: data.includeCallToAction
5890
+ };
5891
+ case "script":
5892
+ return {
5893
+ topic: data.topic ?? "",
5894
+ tone: data.tone ?? "professional",
5895
+ duration: data.duration ?? 60,
5896
+ targetAudience: data.targetAudience ?? "",
5897
+ includeVisuals: true,
5898
+ includeCallToAction: data.includeCallToAction ?? false
5899
+ };
5900
+ default:
5901
+ throw new Error(`Unsupported content type: ${data.contentType}`);
3558
5902
  }
3559
- return null;
3560
5903
  }
3561
5904
 
3562
- // src/providers/groq/groq-container.ts
3563
- var container = null;
3564
- function configureProvider(config) {
3565
- const configStore = new GroqConfigStore();
3566
- const transport = new GroqHttpTransport(configStore);
3567
- const httpClient = new GroqHttpClient({ configStore, transport });
3568
- httpClient.initialize(config);
3569
- const textGeneration = new TextGenerationService(
3570
- httpClient,
3571
- new CacheManager(),
3572
- config.textModel
3573
- );
3574
- const chat = new ChatService(textGeneration);
3575
- container = { groqHttpClient: httpClient, textGeneration, chat };
3576
- return container;
3577
- }
3578
- function getProvider() {
3579
- return container;
5905
+ // src/wizard/image.wizard.ts
5906
+ var IMAGE_WIZARD_STEPS = [
5907
+ {
5908
+ id: "input-prompt",
5909
+ label: "Describe Image",
5910
+ icon: "Image",
5911
+ description: "Describe the image you want to create",
5912
+ validate: (data) => {
5913
+ const prompt = data.prompt;
5914
+ return typeof prompt === "string" && prompt.length > 0;
5915
+ }
5916
+ },
5917
+ {
5918
+ id: "select-style",
5919
+ label: "Style",
5920
+ icon: "Palette",
5921
+ description: "Choose the artistic style",
5922
+ validate: (data) => Boolean(data.style)
5923
+ },
5924
+ {
5925
+ id: "configure",
5926
+ label: "Configure",
5927
+ icon: "Settings",
5928
+ description: "Set format, quality, and quantity",
5929
+ validate: (data) => {
5930
+ const d = data;
5931
+ return Boolean(d.format && d.quality);
5932
+ }
5933
+ },
5934
+ {
5935
+ id: "advanced",
5936
+ label: "Advanced",
5937
+ icon: "Sliders",
5938
+ description: "Color palette and platform optimization",
5939
+ optional: true
5940
+ },
5941
+ { id: "preview", label: "Preview", icon: "Eye", description: "Review your settings" },
5942
+ { id: "generating", label: "Generating", icon: "Sparkles", description: "AI is creating your image" },
5943
+ { id: "results", label: "Results", icon: "Image", description: "Your generated image is ready" }
5944
+ ];
5945
+ function buildImageRequest(data) {
5946
+ return {
5947
+ type: "image",
5948
+ prompt: data.prompt ?? "",
5949
+ style: data.style,
5950
+ format: data.format,
5951
+ quality: data.quality,
5952
+ quantity: data.quantity ?? 1
5953
+ };
3580
5954
  }
3581
- function resetProvider() {
3582
- container?.groqHttpClient.reset();
3583
- container = null;
5955
+
5956
+ // src/wizard/video.wizard.ts
5957
+ var validateSource = (data) => {
5958
+ const d = data;
5959
+ if (d.generationType === "text-to-video") {
5960
+ return typeof d.prompt === "string" && d.prompt.length > 0;
5961
+ }
5962
+ if (d.generationType === "image-to-video") {
5963
+ return typeof d.imageUrl === "string" && d.imageUrl.length > 0;
5964
+ }
5965
+ if (d.generationType === "video-to-video") {
5966
+ return typeof d.sourceVideoUrl === "string" && d.sourceVideoUrl.length > 0 && typeof d.prompt === "string" && d.prompt.length > 0;
5967
+ }
5968
+ return false;
5969
+ };
5970
+ var VIDEO_WIZARD_STEPS = [
5971
+ {
5972
+ id: "select-type",
5973
+ label: "Select Type",
5974
+ icon: "Video",
5975
+ description: "Choose video generation type",
5976
+ validate: (data) => Boolean(data.generationType)
5977
+ },
5978
+ {
5979
+ id: "input-source",
5980
+ label: "Source",
5981
+ icon: "FileInput",
5982
+ description: "Provide your source (prompt, image, or video)",
5983
+ validate: validateSource
5984
+ },
5985
+ {
5986
+ id: "configure",
5987
+ label: "Configure",
5988
+ icon: "Settings",
5989
+ description: "Set duration, aspect ratio, and style",
5990
+ validate: (data) => {
5991
+ const d = data;
5992
+ return Boolean(d.duration && d.aspectRatio);
5993
+ }
5994
+ },
5995
+ { id: "advanced", label: "Advanced", icon: "Sliders", description: "Additional video options", optional: true },
5996
+ { id: "preview", label: "Preview", icon: "Eye", description: "Review your settings" },
5997
+ { id: "generating", label: "Generating", icon: "Sparkles", description: "AI is creating your video" },
5998
+ { id: "results", label: "Results", icon: "Video", description: "Your generated video is ready" }
5999
+ ];
6000
+ function buildVideoRequest(data) {
6001
+ switch (data.generationType) {
6002
+ case "text-to-video":
6003
+ return {
6004
+ type: "video",
6005
+ prompt: data.prompt ?? "",
6006
+ duration: data.duration ?? 5,
6007
+ aspectRatio: data.aspectRatio ?? "16:9",
6008
+ style: data.style,
6009
+ quality: data.quality
6010
+ };
6011
+ case "image-to-video":
6012
+ return {
6013
+ type: "video",
6014
+ imageUrl: data.imageUrl ?? "",
6015
+ prompt: data.prompt,
6016
+ duration: data.duration ?? 5,
6017
+ motion: data.motion ?? "medium",
6018
+ camera: data.camera ?? "static"
6019
+ };
6020
+ case "video-to-video":
6021
+ return {
6022
+ type: "video",
6023
+ sourceVideoUrl: data.sourceVideoUrl ?? "",
6024
+ prompt: data.prompt ?? "",
6025
+ styleTransfer: data.styleTransfer,
6026
+ enhanceQuality: data.enhanceQuality
6027
+ };
6028
+ default:
6029
+ throw new Error(`Unsupported generation type: ${data.generationType}`);
6030
+ }
3584
6031
  }
3585
6032
 
3586
- export { AIError, AIErrorType, ANTHROPIC_API_VERSION, ANTHROPIC_DEFAULT_MAX_TOKENS, ANTHROPIC_MODELS, ANTHROPIC_TIERS, API_ENDPOINTS, AnthropicHttpTransport, AnthropicProvider, AnthropicRequestBuilder, BaseProvider, CacheManager, ChatIdGenerator, ChatService, CompositeExecutor, DEFAULT_API_KEY_RULE, DEFAULT_BASE_URL, DEFAULT_ERROR_CLASSIFICATION_RULE, DEFAULT_GENERATION_CONFIG, DEFAULT_HTTP_STATUS_RULE, DEFAULT_MODELS, DEFAULT_PROMPT_STRATEGY, DEFAULT_RETRY_BACKOFF_MULTIPLIER, DEFAULT_RETRY_INITIAL_DELAY_MS, DEFAULT_RETRY_JITTER_FACTOR, DEFAULT_RETRY_MAX_ATTEMPTS, DEFAULT_RETRY_MAX_DELAY_MS, DebounceManager, DedupeExecutor, FalProvider, GEMINI_TIERS, GROQ_MODELS, GROQ_TIERS, GeminiProvider, GroqChatRequestBuilder, GroqConfigStore, GroqError, AIErrorType as GroqErrorType, GroqHttpClient, GroqHttpTransport, GroqProvider, JSON_CODE_BLOCK_PATTERN, JSON_OBJECT_PATTERN, JsonResponseParser, ProviderError, ProviderFactory, PrunaProvider, QueueExecutor, RequestDeduplicator, RequestQueue, RetryExecutor, RetryManager, SSE_BRIDGE_EVENTS, SSE_DONE_MARKER, SSE_PREFIX, StreamingResponseParser, StructuredPromptBuilder, TEXT_TIERS, TIMEOUTS, TextGenerationService, ThrottleManager, USER_FRIENDLY_ERROR_MESSAGES, asObjectArray, asRecord, asStringArray, asTrimmedOptionalString, buildCacheKey, buildHttpStatusFallback, cacheManager, chatIdGenerator, clampScore, classifyHttpStatus, configureProvider, consumeAnthropicStream, consumeGroqStream, createAI, createAssistantMessage, createFalProvider, createGeminiProvider, createMessage, createPrunaProvider, createSseResponse, createSystemMessage, createUserMessage, debounce, debounceManager, defaultEffortForModel, describeAIErrorType, extractContentFromSsePayload, extractErrorMessageFromJsonBody, extractErrorMessageFromResponse, extractJsonArray, extractJsonCandidate, extractJsonFromCodeFence, extractJsonObject, extractSseDataPayload, extractTextFromMessages, extractTokenUsage, formatErrorForLogging, formatMessagesForDisplay, generateWithPruna, getProvider, isAuthErrorType, isMediaProvider, isRetryableErrorType, isSseDataLine, isSseDoneMarker, isTextProvider, isTextTier, isValidApiKey, mapAnthropicStopReason, mergeTierOverrides, messageFormatter, normalizeSseChunk, oneOfCatalog, promptToMessages, repairCommonJsonDefects, requestDeduplicator, requestQueue, requestStructuredJson, resetProvider, resolveModel, resolveUserFriendlyErrorMessage, resolveUserFriendlyMessage, retryManager, splitSseLines, throttle, throttleManager, toGroqMessages, validateApiKey, withRetry };
6033
+ export { ABTestService, AIContentService, AIError, AIErrorType, ANTHROPIC_API_VERSION, ANTHROPIC_DEFAULT_MAX_TOKENS, ANTHROPIC_MODELS, ANTHROPIC_TIERS, API_ENDPOINTS, ASPECT_RATIO_OPTIONS, ASPECT_RATIO_VALUES, AnalysisService, AnthropicHttpTransport, AnthropicProvider, AnthropicRequestBuilder, AuthenticationError, BaseProvider as BaseAIProvider, BaseProvider, BlogService, CONTENT_WIZARD_STEPS, CacheManager, CancellationError, ChatIdGenerator, ChatService, CompositeExecutor, ContentCalendarService, DEFAULT_API_KEY_RULE, DEFAULT_BASE_URL, DEFAULT_ERROR_CLASSIFICATION_RULE, DEFAULT_GENERATION_CONFIG, DEFAULT_HTTP_STATUS_RULE, DEFAULT_MODELS, DEFAULT_PROMPT_STRATEGY, DEFAULT_RETRY_BACKOFF_MULTIPLIER, DEFAULT_RETRY_INITIAL_DELAY_MS, DEFAULT_RETRY_JITTER_FACTOR, DEFAULT_RETRY_MAX_ATTEMPTS, DEFAULT_RETRY_MAX_DELAY_MS, DebounceManager, DedupeExecutor, FalProvider, GEMINI_TIERS, CREDIT_COSTS as GENERATION_COSTS, GROQ_MODELS, GROQ_TIERS, GeminiProvider, GenerationExecutor, GenerationFailedError, GroqChatRequestBuilder, GroqConfigStore, GroqError, AIErrorType as GroqErrorType, GroqHttpClient, GroqHttpTransport, GroqProvider, IMAGE_QUALITY_OPTIONS, IMAGE_QUALITY_VALUES, IMAGE_WIZARD_STEPS, ImagePromptService, JSON_CODE_BLOCK_PATTERN, JSON_OBJECT_PATTERN, JsonResponseParser, MOTION_OPTIONS, MediaGenerationService, PromptBuilder, ProviderError, ProviderFactory, ProviderQuotaExhaustedError, ProviderUnavailableError, PrunaProvider, QueueExecutor, RequestDeduplicator, RequestQueue, RetryExecutor, RetryManager, SSE_BRIDGE_EVENTS, SSE_DONE_MARKER, SSE_PREFIX, STYLE_PRESETS, SeoService, SocialService, StreamingResponseParser, StructuredPromptBuilder, TEXT_TIERS, TIMEOUTS, TextGenerationService, ThrottleManager, TimeoutError, USER_FRIENDLY_ERROR_MESSAGES, VIDEO_DURATION_OPTIONS, VIDEO_DURATION_OPTIONS_SECONDS, VIDEO_QUALITY_OPTIONS, VIDEO_QUALITY_VALUES, VIDEO_WIZARD_STEPS, ValidationError2 as ValidationError, VideoScriptService, asObjectArray, asRecord, asStringArray, asTrimmedOptionalString, buildCacheKey, buildContentRequest, buildHttpStatusFallback, buildImageRequest, buildVideoRequest, cacheManager, calculateContentCreditCost as calculateCreditCost, chatIdGenerator, clampScore, classifyHttpStatus, configureProvider, consumeAnthropicStream, consumeGroqStream, createAI, createAssistantMessage, createFalProvider, createGeminiProvider, createGroqProvider, createMessage, createProviderFactory, createPrunaProvider, createSseResponse, createSystemMessage, createUserMessage, debounce, debounceManager, defaultEffortForModel, describeAIErrorType, extractContentFromSsePayload, extractErrorMessageFromJsonBody, extractErrorMessageFromResponse, extractJsonArray, extractJsonCandidate, extractJsonFromCodeFence, extractJsonObject, extractSseDataPayload, extractTextFromMessages, extractTokenUsage, formatErrorForLogging, formatMessagesForDisplay, generateImageThenVideo, generate as generatePruna, generateWithPruna, getDimensionsForAspectRatio, getProvider, getRecommendedQuality, isAuthErrorType, isMediaProvider, isRetryableErrorType, isSseDataLine, isSseDoneMarker, isTextProvider, isTextTier, isValidApiKey, mapAnthropicStopReason, mergeTierOverrides, messageFormatter, normalizeSseChunk, oneOfCatalog, pollForResult, promptToMessages, repairCommonJsonDefects, requestDeduplicator, requestQueue, requestStructuredJson, resetProvider, resolveModel, resolveUserFriendlyErrorMessage, resolveUserFriendlyMessage, retryManager, splitSseLines, submitPrediction, throttle, throttleManager, toGroqMessages, uploadImage, validateApiKey, withRetry };
3587
6034
  //# sourceMappingURL=index.js.map
3588
6035
  //# sourceMappingURL=index.js.map