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