@umituz/web-ai 0.0.0-stage → 1.0.0

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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -2
  3. package/dist/ABTesting-DDqNhHtP.d.ts +184 -0
  4. package/dist/ABTesting-DWBtKTMQ.d.cts +184 -0
  5. package/dist/ContentGeneration-B6iK2B33.d.cts +93 -0
  6. package/dist/ContentGeneration-B6iK2B33.d.ts +93 -0
  7. package/dist/ProviderConfig-a55tLhWT.d.cts +99 -0
  8. package/dist/ProviderConfig-a55tLhWT.d.ts +99 -0
  9. package/dist/cache-manager-Df9LLYGA.d.cts +31 -0
  10. package/dist/cache-manager-Df9LLYGA.d.ts +31 -0
  11. package/dist/chat/index.cjs +534 -0
  12. package/dist/chat/index.cjs.map +1 -0
  13. package/dist/chat/index.d.cts +25 -0
  14. package/dist/chat/index.d.ts +25 -0
  15. package/dist/chat/index.js +527 -0
  16. package/dist/chat/index.js.map +1 -0
  17. package/dist/chat.service-DBKq9I2c.d.cts +124 -0
  18. package/dist/chat.service-RQn8UTXD.d.ts +124 -0
  19. package/dist/content/index.cjs +3769 -0
  20. package/dist/content/index.cjs.map +1 -0
  21. package/dist/content/index.d.cts +225 -0
  22. package/dist/content/index.d.ts +225 -0
  23. package/dist/content/index.js +3738 -0
  24. package/dist/content/index.js.map +1 -0
  25. package/dist/core/index.cjs +1040 -0
  26. package/dist/core/index.cjs.map +1 -0
  27. package/dist/core/index.d.cts +232 -0
  28. package/dist/core/index.d.ts +232 -0
  29. package/dist/core/index.js +969 -0
  30. package/dist/core/index.js.map +1 -0
  31. package/dist/create-ai-BhNaz86i.d.ts +268 -0
  32. package/dist/create-ai-DGZFBOA1.d.cts +268 -0
  33. package/dist/hooks/index.cjs +5056 -0
  34. package/dist/hooks/index.cjs.map +1 -0
  35. package/dist/hooks/index.d.cts +210 -0
  36. package/dist/hooks/index.d.ts +210 -0
  37. package/dist/hooks/index.js +5045 -0
  38. package/dist/hooks/index.js.map +1 -0
  39. package/dist/index-DjX9ZKuM.d.cts +14 -0
  40. package/dist/index-DxLvE588.d.ts +14 -0
  41. package/dist/index-fXjxTR5W.d.cts +66 -0
  42. package/dist/index-fXjxTR5W.d.ts +66 -0
  43. package/dist/index.cjs +3158 -0
  44. package/dist/index.cjs.map +1 -0
  45. package/dist/index.d.cts +11 -0
  46. package/dist/index.d.ts +11 -0
  47. package/dist/index.js +3048 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/provider.factory-COsnprLH.d.cts +110 -0
  50. package/dist/provider.factory-CpWFyjbw.d.ts +110 -0
  51. package/dist/providers/index.cjs +2410 -0
  52. package/dist/providers/index.cjs.map +1 -0
  53. package/dist/providers/index.d.cts +333 -0
  54. package/dist/providers/index.d.ts +333 -0
  55. package/dist/providers/index.js +2376 -0
  56. package/dist/providers/index.js.map +1 -0
  57. package/dist/request-deduplicator.util-NDo0_n1y.d.cts +50 -0
  58. package/dist/request-deduplicator.util-NDo0_n1y.d.ts +50 -0
  59. package/dist/wizard/index.cjs +230 -0
  60. package/dist/wizard/index.cjs.map +1 -0
  61. package/dist/wizard/index.d.cts +86 -0
  62. package/dist/wizard/index.d.ts +86 -0
  63. package/dist/wizard/index.js +223 -0
  64. package/dist/wizard/index.js.map +1 -0
  65. package/package.json +111 -4
@@ -0,0 +1,3769 @@
1
+ 'use strict';
2
+
3
+ // src/content/domain/calculations/ContentCreditCost.ts
4
+ var CREDIT_COSTS = {
5
+ "text-to-image": { standard: 1, hd: 2, "4k": 4 },
6
+ "image-to-image": { standard: 1, hd: 2, "4k": 4 },
7
+ "image-to-video": { standard: 3, hd: 5, "4k": 8 },
8
+ "text-to-video": { standard: 5, hd: 8, "4k": 12 }
9
+ };
10
+ var DEFAULT_CREDIT_COST = 0;
11
+ function toQualityTier(quality) {
12
+ if (quality === "4k") return "4k";
13
+ if (quality === "hd" || quality === "1080p") return "hd";
14
+ return "standard";
15
+ }
16
+ function calculateContentCreditCost(type, quality = "standard") {
17
+ const tierCosts = CREDIT_COSTS[type];
18
+ if (!tierCosts) return DEFAULT_CREDIT_COST;
19
+ return tierCosts[toQualityTier(quality)] ?? tierCosts.standard;
20
+ }
21
+
22
+ // src/content/domain/limits/ImageFormatOptions.ts
23
+ var ASPECT_RATIO_OPTIONS = [
24
+ { value: "1:1", label: "Square", width: 1024, height: 1024 },
25
+ { value: "16:9", label: "Landscape", width: 1920, height: 1080 },
26
+ { value: "9:16", label: "Portrait", width: 1080, height: 1920 },
27
+ { value: "4:3", label: "Standard", width: 1440, height: 1080 },
28
+ { value: "3:4", label: "Tall", width: 1080, height: 1440 }
29
+ ];
30
+ var IMAGE_QUALITY_OPTIONS = [
31
+ { value: "standard", label: "Standard", description: "1024x1024, good quality", resolution: "1024x1024" },
32
+ { value: "hd", label: "HD", description: "1920x1080, high quality", resolution: "1920x1080" },
33
+ { value: "4k", label: "4K", description: "3840x2160, ultra quality", resolution: "3840x2160" }
34
+ ];
35
+ var VIDEO_QUALITY_OPTIONS = [
36
+ { value: "720p", label: "720p HD", resolution: "1280x720" },
37
+ { value: "1080p", label: "1080p Full HD", resolution: "1920x1080" },
38
+ { value: "4k", label: "4K Ultra HD", resolution: "3840x2160" }
39
+ ];
40
+ var VIDEO_DURATION_OPTIONS_SECONDS = [2, 3, 4, 5, 6, 7, 8, 9, 10];
41
+ var MOTION_OPTIONS = [
42
+ { value: "slow", label: "Slow", description: "Gentle movement" },
43
+ { value: "medium", label: "Medium", description: "Balanced motion" },
44
+ { value: "fast", label: "Fast", description: "Dynamic movement" }
45
+ ];
46
+ var STYLE_PRESETS = [
47
+ { value: "realistic", label: "Realistic", promptFragment: "realistic, photograph, detailed" },
48
+ { value: "artistic", label: "Artistic", promptFragment: "artistic, painting, creative" },
49
+ { value: "anime", label: "Anime", promptFragment: "anime style, manga, vibrant colors" },
50
+ { value: "cinematic", label: "Cinematic", promptFragment: "cinematic, movie scene, dramatic lighting" },
51
+ { value: "vintage", label: "Vintage", promptFragment: "vintage, retro, film grain" },
52
+ { value: "fantasy", label: "Fantasy", promptFragment: "fantasy, magical, ethereal" },
53
+ { value: "minimalist", label: "Minimalist", promptFragment: "minimalist, clean, simple" },
54
+ { value: "abstract", label: "Abstract", promptFragment: "abstract, geometric, artistic" }
55
+ ];
56
+
57
+ // src/content/domain/calculations/AspectRatioDimensions.ts
58
+ var FALLBACK_DIMENSIONS = { width: 1024, height: 1024 };
59
+ function resolveDimensionsForAspectRatio(ratio) {
60
+ const match = ASPECT_RATIO_OPTIONS.find((option) => option.value === ratio);
61
+ return match ? { width: match.width, height: match.height } : FALLBACK_DIMENSIONS;
62
+ }
63
+
64
+ // src/content/domain/predicates/QualityRecommenderByRatio.ts
65
+ var WIDE_RATIOS = /* @__PURE__ */ new Set(["16:9", "4:3"]);
66
+ function recommendQualityForAspectRatio(ratio) {
67
+ return WIDE_RATIOS.has(ratio) ? "hd" : "standard";
68
+ }
69
+
70
+ // src/content/domain/constants/GenerationConstants.ts
71
+ var ASPECT_RATIO_VALUES = ASPECT_RATIO_OPTIONS.map((o) => o.value);
72
+ var IMAGE_QUALITY_VALUES = IMAGE_QUALITY_OPTIONS.map((o) => o.value);
73
+ var VIDEO_QUALITY_VALUES = VIDEO_QUALITY_OPTIONS.map((o) => o.value);
74
+ var VIDEO_DURATION_OPTIONS = VIDEO_DURATION_OPTIONS_SECONDS;
75
+ var getDimensionsForAspectRatio = resolveDimensionsForAspectRatio;
76
+ var getRecommendedQuality = recommendQualityForAspectRatio;
77
+
78
+ // src/content/domain/errors/AIErrors.ts
79
+ var AIError = class _AIError extends Error {
80
+ code;
81
+ cause;
82
+ constructor(message, code, cause) {
83
+ super(message);
84
+ this.name = "AIError";
85
+ this.code = code;
86
+ this.cause = cause;
87
+ }
88
+ static from(error, fallbackCode = "UNKNOWN") {
89
+ if (error instanceof _AIError) return error;
90
+ if (error instanceof Error) {
91
+ return new _AIError(error.message, mapMessageToCode(error.message, fallbackCode), error);
92
+ }
93
+ return new _AIError(String(error), fallbackCode, error);
94
+ }
95
+ };
96
+ function mapMessageToCode(message, fallback) {
97
+ const lower = message.toLowerCase();
98
+ if (lower.includes("validation")) return "VALIDATION_ERROR";
99
+ if (lower.includes("quota")) return "PROVIDER_QUOTA_EXHAUSTED";
100
+ if (lower.includes("unavailable")) return "PROVIDER_UNAVAILABLE";
101
+ if (lower.includes("timeout") || lower.includes("aborted")) {
102
+ return lower.includes("aborted") ? "ABORTED" : "TIMEOUT";
103
+ }
104
+ if (lower.includes("parse") || lower.includes("json")) return "PARSE_ERROR";
105
+ if (lower.includes("no result")) return "NO_RESULT";
106
+ if (lower.includes("network") || lower.includes("fetch")) return "NETWORK_ERROR";
107
+ return fallback;
108
+ }
109
+ var ValidationError = class extends AIError {
110
+ constructor(message) {
111
+ super(message, "VALIDATION_ERROR");
112
+ this.name = "ValidationError";
113
+ }
114
+ };
115
+ var ProviderUnavailableError = class extends AIError {
116
+ constructor(providerId) {
117
+ super(`Provider ${providerId} is not available`, "PROVIDER_UNAVAILABLE");
118
+ this.name = "ProviderUnavailableError";
119
+ }
120
+ };
121
+ var ProviderQuotaExhaustedError = class extends AIError {
122
+ constructor(providerId) {
123
+ super(`Provider ${providerId} quota exhausted`, "PROVIDER_QUOTA_EXHAUSTED");
124
+ this.name = "ProviderQuotaExhaustedError";
125
+ }
126
+ };
127
+
128
+ // src/core/errors/ai-error.ts
129
+ var AIError2 = class extends Error {
130
+ type;
131
+ cause;
132
+ metadata;
133
+ constructor(type, message, cause, metadata = {}) {
134
+ super(message);
135
+ this.name = "AIError";
136
+ this.type = type;
137
+ this.cause = cause;
138
+ this.metadata = metadata;
139
+ }
140
+ toString() {
141
+ const statusPart = typeof this.metadata.status === "number" ? ` (HTTP ${this.metadata.status})` : "";
142
+ return `${this.name} [${this.type}]: ${this.message}${statusPart}`;
143
+ }
144
+ toJSON() {
145
+ return {
146
+ name: this.name,
147
+ type: this.type,
148
+ message: this.message,
149
+ metadata: this.metadata
150
+ };
151
+ }
152
+ };
153
+
154
+ // src/core/errors/error-classifier.ts
155
+ var DEFAULT_RETRYABLE_TYPES = /* @__PURE__ */ new Set([
156
+ "NETWORK_ERROR" /* NETWORK_ERROR */,
157
+ "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */,
158
+ "SERVER_ERROR" /* SERVER_ERROR */
159
+ ]);
160
+ var DEFAULT_AUTH_TYPES = /* @__PURE__ */ new Set([
161
+ "INVALID_API_KEY" /* INVALID_API_KEY */
162
+ ]);
163
+ var DEFAULT_ERROR_CLASSIFICATION_RULE = {
164
+ retryable: DEFAULT_RETRYABLE_TYPES,
165
+ auth: DEFAULT_AUTH_TYPES
166
+ };
167
+ function isRetryableErrorType(type, rule = DEFAULT_ERROR_CLASSIFICATION_RULE) {
168
+ return rule.retryable.has(type);
169
+ }
170
+
171
+ // src/core/errors/http-status-classifier.ts
172
+ var DEFAULT_HTTP_STATUS_RULE = {
173
+ unauthorizedStatuses: /* @__PURE__ */ new Set([401, 403]),
174
+ rateLimitedStatus: 429,
175
+ serverErrorThreshold: 500,
176
+ clientErrorThreshold: 400
177
+ };
178
+ function classifyHttpStatus(status, rule = DEFAULT_HTTP_STATUS_RULE) {
179
+ if (rule.unauthorizedStatuses.has(status)) return "INVALID_API_KEY" /* INVALID_API_KEY */;
180
+ if (status === rule.rateLimitedStatus) return "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */;
181
+ if (status >= rule.serverErrorThreshold) return "SERVER_ERROR" /* SERVER_ERROR */;
182
+ if (status >= rule.clientErrorThreshold) return "CLIENT_ERROR" /* CLIENT_ERROR */;
183
+ return "UNKNOWN_ERROR" /* UNKNOWN_ERROR */;
184
+ }
185
+
186
+ // src/core/errors/response-error-message-extractor.ts
187
+ function extractErrorMessageFromJsonBody(body) {
188
+ if (!body || typeof body !== "object") return null;
189
+ const candidate = body;
190
+ const message = candidate.error?.message ?? candidate.message;
191
+ if (typeof message === "string" && message.length > 0) return message;
192
+ return null;
193
+ }
194
+ function buildHttpStatusFallback(status, statusText) {
195
+ const trimmedStatusText = statusText.trim();
196
+ if (trimmedStatusText.length > 0) {
197
+ return `HTTP ${status}: ${trimmedStatusText}`;
198
+ }
199
+ return `HTTP ${status}`;
200
+ }
201
+ async function extractErrorMessageFromResponse(response) {
202
+ const body = await response.json().catch(() => null);
203
+ const fromBody = extractErrorMessageFromJsonBody(body);
204
+ if (fromBody) return fromBody;
205
+ return buildHttpStatusFallback(response.status, response.statusText);
206
+ }
207
+
208
+ // src/providers/base/provider.types.ts
209
+ function isTextProvider(provider) {
210
+ return provider.capabilities.includes("text");
211
+ }
212
+
213
+ // src/content/domain/limits/ProviderTimingConfig.ts
214
+ var ProviderTimingConfig = {
215
+ DEFAULT_TIMEOUT_MS: 3e4,
216
+ DEFAULT_RETRY_ATTEMPTS: 3,
217
+ FAL_TIMEOUT_MS: 12e4,
218
+ FAL_RETRY_ATTEMPTS: 2,
219
+ GEMINI_TIMEOUT_MS: 6e4,
220
+ GEMINI_RETRY_ATTEMPTS: 3,
221
+ HEALTH_CHECK_TTL_MS: 5 * 60 * 1e3,
222
+ RETRY_BACKOFF_BASE_MS: 1e3
223
+ };
224
+
225
+ // src/providers/factory/provider.factory.ts
226
+ var ProviderFactory = class {
227
+ providers = /* @__PURE__ */ new Map();
228
+ healthCache = /* @__PURE__ */ new Map();
229
+ priority;
230
+ fallbackEnabled;
231
+ defaultTier;
232
+ constructor(config = {}) {
233
+ this.priority = config.priority ?? [];
234
+ this.fallbackEnabled = config.fallbackEnabled ?? true;
235
+ this.defaultTier = config.defaultTier ?? "balanced";
236
+ }
237
+ register(provider) {
238
+ this.providers.set(provider.id, provider);
239
+ }
240
+ getProvider(id) {
241
+ return this.providers.get(id);
242
+ }
243
+ getAllProviders() {
244
+ return Array.from(this.providers.values());
245
+ }
246
+ getByCapability(capability) {
247
+ return this.getAllProviders().filter(
248
+ (provider) => provider.capabilities.includes(capability)
249
+ );
250
+ }
251
+ getTextProviders() {
252
+ return this.getAllProviders().filter(isTextProvider);
253
+ }
254
+ hasTextProvider() {
255
+ return this.getByCapability("text").length > 0;
256
+ }
257
+ /* ---------- neutral text surface ---------- */
258
+ /**
259
+ * Single-chat entry point: resolves a healthy text provider by priority,
260
+ * injects the default tier when the request names neither model nor tier,
261
+ * and walks the fallback chain on failure. Providers map tiers onto their
262
+ * own models, so switching Groq→Anthropic is a config change.
263
+ */
264
+ async chat(request) {
265
+ const effective = request.model || request.tier ? request : { ...request, tier: this.defaultTier };
266
+ return this.executeWithFallback(
267
+ "text",
268
+ (provider) => provider.generate(effective)
269
+ );
270
+ }
271
+ async chatStream(request, callbacks) {
272
+ const effective = request.model || request.tier ? request : { ...request, tier: this.defaultTier };
273
+ let lastError;
274
+ const attemptCallbacks = {
275
+ onChunk: callbacks.onChunk,
276
+ onComplete: callbacks.onComplete
277
+ };
278
+ const providers = this.getByCapability("text");
279
+ if (providers.length === 0) {
280
+ throw this.noProviderError("text");
281
+ }
282
+ const ordered = this.orderByPriority(providers);
283
+ for (const provider of ordered) {
284
+ if (!this.isProviderHealthy(provider.id)) continue;
285
+ try {
286
+ return await provider.stream(effective, attemptCallbacks);
287
+ } catch (error) {
288
+ lastError = error;
289
+ this.markDegraded(provider.id);
290
+ if (!this.fallbackEnabled) {
291
+ callbacks.onError?.(error instanceof Error ? error : new Error(String(error)));
292
+ throw error;
293
+ }
294
+ }
295
+ }
296
+ const failure = new AIError2(
297
+ "SERVER_ERROR" /* SERVER_ERROR */,
298
+ `All providers failed for capability 'text'`,
299
+ lastError instanceof Error ? lastError : void 0
300
+ );
301
+ callbacks.onError?.(lastError instanceof Error ? lastError : failure);
302
+ throw failure;
303
+ }
304
+ /** AsyncGenerator adapter over chatStream — A's familiar consumption shape. */
305
+ async *streamText(request) {
306
+ const queue = [];
307
+ let wake = null;
308
+ let finished = false;
309
+ let failure;
310
+ const notify = () => {
311
+ const resolve = wake;
312
+ wake = null;
313
+ resolve?.();
314
+ };
315
+ const completion = this.chatStream(request, {
316
+ onChunk: (chunk) => {
317
+ queue.push(chunk);
318
+ notify();
319
+ }
320
+ }).then(
321
+ () => {
322
+ finished = true;
323
+ notify();
324
+ },
325
+ (error) => {
326
+ failure = error;
327
+ finished = true;
328
+ notify();
329
+ }
330
+ );
331
+ while (!finished || queue.length > 0) {
332
+ if (queue.length === 0 && !finished) {
333
+ await new Promise((resolve) => {
334
+ wake = resolve;
335
+ });
336
+ }
337
+ while (queue.length > 0) {
338
+ yield queue.shift();
339
+ }
340
+ if (failure) throw failure;
341
+ }
342
+ await completion;
343
+ }
344
+ async generateJson(request) {
345
+ const effective = request.model || request.tier ? request : { ...request, tier: this.defaultTier };
346
+ return this.executeWithFallback(
347
+ "text",
348
+ (provider) => provider.generateStructured(effective)
349
+ );
350
+ }
351
+ /* ---------- content-layer string facade (ITextGenerator) ---------- */
352
+ async generateText(prompt, options = {}) {
353
+ const result = await this.chat({
354
+ messages: [{ role: "user", content: prompt }],
355
+ model: options.model,
356
+ maxTokens: options.maxTokens,
357
+ temperature: options.temperature,
358
+ topP: options.topP
359
+ });
360
+ return result.text;
361
+ }
362
+ generateTextStream(prompt, options = {}) {
363
+ return this.streamText({
364
+ messages: [{ role: "user", content: prompt }],
365
+ model: options.model,
366
+ maxTokens: options.maxTokens,
367
+ temperature: options.temperature,
368
+ topP: options.topP
369
+ });
370
+ }
371
+ /* ---------- media surface ---------- */
372
+ async generateImage(request) {
373
+ return this.executeWithFallback(
374
+ "image",
375
+ (provider) => provider.generateImage(request)
376
+ );
377
+ }
378
+ async generateVideo(request) {
379
+ return this.executeWithFallback(
380
+ "video",
381
+ (provider) => provider.generateVideo(request)
382
+ );
383
+ }
384
+ async imageToVideo(request) {
385
+ return this.executeWithFallback(
386
+ "video",
387
+ (provider) => provider.imageToVideo(request)
388
+ );
389
+ }
390
+ async videoToVideo(request) {
391
+ return this.executeWithFallback(
392
+ "video",
393
+ (provider) => provider.videoToVideo(request)
394
+ );
395
+ }
396
+ /* ---------- health ---------- */
397
+ async checkProviderHealth(providerId, forceRefresh = false) {
398
+ const provider = this.providers.get(providerId);
399
+ if (!provider) {
400
+ return "unavailable";
401
+ }
402
+ const cached = this.healthCache.get(providerId);
403
+ const now = Date.now();
404
+ const ttl = ProviderTimingConfig.HEALTH_CHECK_TTL_MS;
405
+ if (!forceRefresh && cached && now - cached.timestamp < ttl) {
406
+ return cached.health;
407
+ }
408
+ try {
409
+ const health = await provider.healthCheck();
410
+ this.healthCache.set(providerId, { health, timestamp: now });
411
+ return health;
412
+ } catch {
413
+ this.healthCache.set(providerId, { health: "unavailable", timestamp: now });
414
+ return "unavailable";
415
+ }
416
+ }
417
+ isProviderHealthy(providerId) {
418
+ const cached = this.healthCache.get(providerId);
419
+ if (!cached) {
420
+ return true;
421
+ }
422
+ return cached.health === "healthy" || cached.health === "degraded";
423
+ }
424
+ async refreshAllHealth() {
425
+ await Promise.all(
426
+ Array.from(this.providers.keys()).map((id) => this.checkProviderHealth(id))
427
+ );
428
+ }
429
+ /* ---------- internals ---------- */
430
+ async executeWithFallback(capability, fn) {
431
+ const providers = this.getByCapability(capability);
432
+ if (providers.length === 0) {
433
+ throw this.noProviderError(capability);
434
+ }
435
+ let lastError;
436
+ for (const provider of this.orderByPriority(providers)) {
437
+ if (!this.isProviderHealthy(provider.id)) {
438
+ continue;
439
+ }
440
+ try {
441
+ return await fn(provider);
442
+ } catch (error) {
443
+ lastError = error;
444
+ this.markDegraded(provider.id);
445
+ if (!this.fallbackEnabled) {
446
+ throw error;
447
+ }
448
+ }
449
+ }
450
+ throw new AIError2(
451
+ "SERVER_ERROR" /* SERVER_ERROR */,
452
+ `All providers failed for capability '${capability}'`,
453
+ lastError instanceof Error ? lastError : void 0
454
+ );
455
+ }
456
+ orderByPriority(providers) {
457
+ if (this.priority.length === 0) {
458
+ return providers;
459
+ }
460
+ const ordered = [];
461
+ for (const providerId of this.priority) {
462
+ const provider = providers.find((p) => p.id === providerId);
463
+ if (provider) ordered.push(provider);
464
+ }
465
+ for (const provider of providers) {
466
+ if (!this.priority.includes(provider.id)) {
467
+ ordered.push(provider);
468
+ }
469
+ }
470
+ return ordered;
471
+ }
472
+ markDegraded(providerId) {
473
+ const cached = this.healthCache.get(providerId);
474
+ if (cached) {
475
+ this.healthCache.set(providerId, { health: "degraded", timestamp: cached.timestamp });
476
+ }
477
+ }
478
+ noProviderError(capability) {
479
+ return new AIError2(
480
+ "MISSING_CONFIG" /* MISSING_CONFIG */,
481
+ `No provider registered for capability '${capability}' \u2014 create one with createAI() and include its config block`
482
+ );
483
+ }
484
+ };
485
+
486
+ // src/core/constants/protocol.constants.ts
487
+ var SSE_PREFIX = "data: ";
488
+ var SSE_DONE_MARKER = "[DONE]";
489
+ var JSON_CODE_BLOCK_PATTERN = /```json\n([\s\S]*?)\n```/;
490
+ var JSON_OBJECT_PATTERN = /\{[\s\S]*\}/;
491
+
492
+ // src/core/parsers/sse-lines.ts
493
+ function isSseDataLine(line) {
494
+ return line.startsWith(SSE_PREFIX);
495
+ }
496
+ function extractSseDataPayload(line) {
497
+ return line.slice(SSE_PREFIX.length);
498
+ }
499
+ function isSseDoneMarker(payload) {
500
+ return payload === SSE_DONE_MARKER;
501
+ }
502
+
503
+ // src/core/parsers/json-content-extractor.ts
504
+ function extractJsonFromCodeFence(content) {
505
+ const match = content.match(JSON_CODE_BLOCK_PATTERN);
506
+ if (match && match[1]) return match[1];
507
+ return null;
508
+ }
509
+ function extractJsonObject(content) {
510
+ const match = content.match(JSON_OBJECT_PATTERN);
511
+ if (match && match[0]) return match[0];
512
+ return null;
513
+ }
514
+ function extractJsonCandidate(content) {
515
+ return extractJsonFromCodeFence(content) ?? extractJsonObject(content) ?? content;
516
+ }
517
+
518
+ // src/core/parsers/json-response.parser.ts
519
+ var JsonResponseParser = class {
520
+ parse(rawContent) {
521
+ const candidate = extractJsonCandidate(rawContent);
522
+ try {
523
+ return JSON.parse(candidate);
524
+ } catch (error) {
525
+ throw new AIError2(
526
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
527
+ "Failed to parse structured response as JSON",
528
+ error instanceof Error ? error : void 0
529
+ );
530
+ }
531
+ }
532
+ };
533
+
534
+ // src/core/parsers/structured-prompt.builder.ts
535
+ var StructuredPromptBuilder = class _StructuredPromptBuilder {
536
+ schema;
537
+ request;
538
+ constructor(request) {
539
+ this.request = request;
540
+ }
541
+ static for(request) {
542
+ return new _StructuredPromptBuilder(request);
543
+ }
544
+ withSchema(schema) {
545
+ this.schema = schema;
546
+ return this;
547
+ }
548
+ build() {
549
+ const schemaSection = this.schema ? `Expected JSON schema:
550
+ ${JSON.stringify(this.schema, null, 2)}
551
+
552
+ ` : "";
553
+ return "Please generate a JSON response for the following request.\n\n" + schemaSection + `Request:
554
+ ${this.request}
555
+
556
+ Respond only with valid JSON, formatted as:
557
+ \`\`\`json
558
+ {...}
559
+ \`\`\``;
560
+ }
561
+ };
562
+
563
+ // src/core/tiers/index.ts
564
+ function resolveModel(tiers, tier, providerId) {
565
+ const model = tiers[tier] ?? tiers.balanced;
566
+ if (!model) {
567
+ throw new Error(
568
+ `${providerId} has no model configured for tier "${tier}" \u2014 pass an explicit model or configure the tier map`
569
+ );
570
+ }
571
+ return model;
572
+ }
573
+ function mergeTierOverrides(defaults, overrides) {
574
+ if (!overrides) return defaults;
575
+ return { ...defaults, ...overrides };
576
+ }
577
+
578
+ // src/providers/anthropic/anthropic.constants.ts
579
+ var ANTHROPIC_MODELS = {
580
+ OPUS_5_5: "claude-opus-5-5",
581
+ SONNET_5_5: "claude-sonnet-5-5",
582
+ HAIKU_5_5: "claude-haiku-5-5"
583
+ };
584
+ var ANTHROPIC_TIERS = {
585
+ fast: ANTHROPIC_MODELS.HAIKU_5_5,
586
+ balanced: ANTHROPIC_MODELS.SONNET_5_5,
587
+ powerful: ANTHROPIC_MODELS.OPUS_5_5
588
+ };
589
+ var ANTHROPIC_API_VERSION = "2026-10-21";
590
+ var ANTHROPIC_DEFAULT_MAX_TOKENS = 16e3;
591
+ var ANTHROPIC_BASE_URL = "https://api.anthropic.com";
592
+ var ANTHROPIC_MESSAGES_PATH = "/v1/messages";
593
+ var ANTHROPIC_MODELS_PATH = "/v1/models";
594
+ var ANTHROPIC_TIMEOUTS = {
595
+ DEFAULT: 6e4,
596
+ STREAMING: 12e4
597
+ };
598
+ var ANTHROPIC_HEADERS = {
599
+ CONTENT_TYPE: "Content-Type",
600
+ APPLICATION_JSON: "application/json",
601
+ API_KEY: "x-api-key",
602
+ VERSION: "anthropic-version"
603
+ };
604
+ var ANTHROPIC_DEFAULT_EFFORT = {
605
+ [ANTHROPIC_MODELS.OPUS_5_5]: "medium",
606
+ [ANTHROPIC_MODELS.SONNET_5_5]: "high",
607
+ [ANTHROPIC_MODELS.HAIKU_5_5]: "medium"
608
+ };
609
+
610
+ // src/providers/anthropic/anthropic-http.transport.ts
611
+ var AnthropicHttpTransport = class {
612
+ constructor(config) {
613
+ this.config = config;
614
+ }
615
+ config;
616
+ async postMessages(request, options = {}) {
617
+ const url = `${this.baseUrl()}${ANTHROPIC_MESSAGES_PATH}`;
618
+ const timeout = this.config.timeoutMs ?? ANTHROPIC_TIMEOUTS.DEFAULT;
619
+ const controller = new AbortController();
620
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
621
+ const onExternalAbort = () => controller.abort();
622
+ options.signal?.addEventListener("abort", onExternalAbort);
623
+ try {
624
+ const response = await fetch(url, {
625
+ method: "POST",
626
+ headers: this.buildHeaders(),
627
+ body: JSON.stringify({ ...request, stream: false }),
628
+ signal: controller.signal
629
+ });
630
+ if (!response.ok) await this.throwFromErrorResponse(response);
631
+ return await response.json();
632
+ } catch (error) {
633
+ throw this.normalizeError(error);
634
+ } finally {
635
+ clearTimeout(timeoutId);
636
+ options.signal?.removeEventListener("abort", onExternalAbort);
637
+ }
638
+ }
639
+ async streamMessages(request, options = {}) {
640
+ const url = `${this.baseUrl()}${ANTHROPIC_MESSAGES_PATH}`;
641
+ const timeout = this.config.timeoutMs ?? ANTHROPIC_TIMEOUTS.STREAMING;
642
+ const controller = new AbortController();
643
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
644
+ const onExternalAbort = () => controller.abort();
645
+ options.signal?.addEventListener("abort", onExternalAbort);
646
+ try {
647
+ const response = await fetch(url, {
648
+ method: "POST",
649
+ headers: this.buildHeaders(),
650
+ body: JSON.stringify({ ...request, stream: true }),
651
+ signal: controller.signal
652
+ });
653
+ if (!response.ok) await this.throwFromErrorResponse(response);
654
+ if (!response.body) {
655
+ throw new AIError2("NETWORK_ERROR" /* NETWORK_ERROR */, "Response body is null");
656
+ }
657
+ return response.body;
658
+ } catch (error) {
659
+ options.signal?.removeEventListener("abort", onExternalAbort);
660
+ throw this.normalizeError(error);
661
+ } finally {
662
+ clearTimeout(timeoutId);
663
+ }
664
+ }
665
+ async listModels() {
666
+ const url = `${this.baseUrl()}${ANTHROPIC_MODELS_PATH}`;
667
+ return fetch(url, {
668
+ method: "GET",
669
+ headers: this.buildHeaders()
670
+ });
671
+ }
672
+ baseUrl() {
673
+ return this.config.baseUrl ?? ANTHROPIC_BASE_URL;
674
+ }
675
+ buildHeaders() {
676
+ return {
677
+ [ANTHROPIC_HEADERS.CONTENT_TYPE]: ANTHROPIC_HEADERS.APPLICATION_JSON,
678
+ [ANTHROPIC_HEADERS.API_KEY]: this.config.apiKey,
679
+ [ANTHROPIC_HEADERS.VERSION]: ANTHROPIC_API_VERSION
680
+ };
681
+ }
682
+ async throwFromErrorResponse(response) {
683
+ const errorMessage = await extractErrorMessageFromResponse(response);
684
+ const metadata = {
685
+ status: response.status,
686
+ url: response.url
687
+ };
688
+ throw new AIError2(
689
+ classifyHttpStatus(response.status),
690
+ errorMessage,
691
+ void 0,
692
+ metadata
693
+ );
694
+ }
695
+ normalizeError(error) {
696
+ if (error instanceof AIError2) return error;
697
+ if (error instanceof Error) {
698
+ if (error.name === "AbortError") {
699
+ return new AIError2(
700
+ "ABORT_ERROR" /* ABORT_ERROR */,
701
+ "Request was aborted by the client",
702
+ error
703
+ );
704
+ }
705
+ if (error.name === "TypeError" && error.message.toLowerCase().includes("network")) {
706
+ return new AIError2(
707
+ "NETWORK_ERROR" /* NETWORK_ERROR */,
708
+ "Network error: Unable to connect to the AI service",
709
+ error
710
+ );
711
+ }
712
+ }
713
+ return new AIError2(
714
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
715
+ error instanceof Error ? error.message : "Unknown error occurred",
716
+ error instanceof Error ? error : void 0
717
+ );
718
+ }
719
+ };
720
+
721
+ // src/providers/anthropic/anthropic-request.builder.ts
722
+ var AnthropicRequestBuilder = class _AnthropicRequestBuilder {
723
+ model;
724
+ messages = [];
725
+ systemPrompt;
726
+ maxTokens = ANTHROPIC_DEFAULT_MAX_TOKENS;
727
+ effort;
728
+ stopSequences;
729
+ streamEnabled = false;
730
+ constructor(model) {
731
+ this.model = model;
732
+ }
733
+ static for(model) {
734
+ return new _AnthropicRequestBuilder(model);
735
+ }
736
+ withRequest(request) {
737
+ this.messages = toAnthropicMessages(request.messages);
738
+ this.systemPrompt = request.systemPrompt;
739
+ if (request.maxTokens !== void 0) this.maxTokens = request.maxTokens;
740
+ if (request.stopSequences !== void 0 && request.stopSequences.length > 0) {
741
+ this.stopSequences = [...request.stopSequences];
742
+ }
743
+ return this;
744
+ }
745
+ withEffort(effort) {
746
+ this.effort = effort;
747
+ return this;
748
+ }
749
+ withStream(stream) {
750
+ this.streamEnabled = stream;
751
+ return this;
752
+ }
753
+ withSystemPromptOverride(systemPrompt) {
754
+ this.systemPrompt = systemPrompt;
755
+ return this;
756
+ }
757
+ withMessagesOverride(messages) {
758
+ this.messages = messages;
759
+ return this;
760
+ }
761
+ build() {
762
+ const request = {
763
+ model: this.model,
764
+ messages: this.messages,
765
+ max_tokens: this.maxTokens,
766
+ output_config: {
767
+ effort: this.effort ?? defaultEffortForModel(this.model)
768
+ }
769
+ };
770
+ if (this.systemPrompt !== void 0 && this.systemPrompt.length > 0) {
771
+ request.system = this.systemPrompt;
772
+ }
773
+ if (this.stopSequences !== void 0) {
774
+ request.stop_sequences = this.stopSequences;
775
+ }
776
+ if (this.streamEnabled) {
777
+ request.stream = true;
778
+ }
779
+ return request;
780
+ }
781
+ };
782
+ function toAnthropicMessages(messages) {
783
+ return messages.filter((message) => message.role !== "system" && message.content.length > 0).map((message) => ({
784
+ role: message.role,
785
+ content: message.content
786
+ }));
787
+ }
788
+ function defaultEffortForModel(model) {
789
+ return ANTHROPIC_DEFAULT_EFFORT[model] ?? "medium";
790
+ }
791
+
792
+ // src/providers/anthropic/anthropic-stream.parser.ts
793
+ function mapAnthropicStopReason(reason) {
794
+ if (reason === "end_turn") return "stop";
795
+ if (reason === "stop_sequence") return "stop";
796
+ if (reason === "max_tokens") return "length";
797
+ if (reason === "refusal") return "refusal";
798
+ return void 0;
799
+ }
800
+ async function consumeAnthropicStream(stream, callbacks) {
801
+ const reader = stream.getReader();
802
+ const decoder = new TextDecoder();
803
+ const chunks = [];
804
+ let buffer = "";
805
+ let model = null;
806
+ let inputTokens;
807
+ let outputTokens;
808
+ let finishReason;
809
+ const consumeLine = (rawLine) => {
810
+ const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
811
+ const trimmed = line.trim();
812
+ if (trimmed.length === 0 || !isSseDataLine(trimmed)) return;
813
+ const payload = extractSseDataPayload(trimmed);
814
+ let event;
815
+ try {
816
+ event = JSON.parse(payload);
817
+ } catch {
818
+ return;
819
+ }
820
+ switch (event.type) {
821
+ case "message_start":
822
+ model = event.message.model;
823
+ if (event.message.usage?.input_tokens !== void 0) {
824
+ inputTokens = event.message.usage.input_tokens;
825
+ }
826
+ break;
827
+ case "content_block_delta":
828
+ if (typeof event.delta.text === "string" && event.delta.text.length > 0) {
829
+ chunks.push(event.delta.text);
830
+ callbacks.onChunk?.(event.delta.text);
831
+ }
832
+ break;
833
+ case "message_delta":
834
+ if (event.delta?.stop_reason !== void 0 && event.delta.stop_reason !== null) {
835
+ finishReason = mapAnthropicStopReason(event.delta.stop_reason);
836
+ }
837
+ if (event.usage?.output_tokens !== void 0) {
838
+ outputTokens = event.usage.output_tokens;
839
+ }
840
+ break;
841
+ case "message_stop":
842
+ break;
843
+ case "error":
844
+ throw new AIError2(
845
+ event.error.type === "rate_limit_error" ? "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ : "SERVER_ERROR" /* SERVER_ERROR */,
846
+ event.error.message || "Anthropic stream failed mid-flight"
847
+ );
848
+ }
849
+ };
850
+ try {
851
+ while (true) {
852
+ const { done, value } = await reader.read();
853
+ if (done) break;
854
+ buffer += decoder.decode(value, { stream: true });
855
+ const lines = buffer.split("\n");
856
+ buffer = lines.pop() ?? "";
857
+ for (const line of lines) consumeLine(line);
858
+ }
859
+ buffer += decoder.decode();
860
+ if (buffer.length > 0) consumeLine(buffer);
861
+ } catch (error) {
862
+ if (error instanceof AIError2) throw error;
863
+ throw new AIError2(
864
+ "NETWORK_ERROR" /* NETWORK_ERROR */,
865
+ "Stream was interrupted",
866
+ error instanceof Error ? error : void 0
867
+ );
868
+ } finally {
869
+ reader.releaseLock();
870
+ }
871
+ return {
872
+ text: chunks.join(""),
873
+ model,
874
+ inputTokens,
875
+ outputTokens,
876
+ finishReason
877
+ };
878
+ }
879
+
880
+ // src/providers/anthropic/anthropic.provider.ts
881
+ var REFUSAL_FALLBACK_TEXT = "I'm not able to help with that request. Could you rephrase or ask something else?";
882
+ var AnthropicProvider = class {
883
+ constructor(config) {
884
+ this.config = config;
885
+ this.transport = new AnthropicHttpTransport(config);
886
+ this.tiers = mergeTierOverrides(ANTHROPIC_TIERS, config.models);
887
+ }
888
+ config;
889
+ id = "anthropic";
890
+ name = "Anthropic";
891
+ capabilities = ["text"];
892
+ transport;
893
+ tiers;
894
+ jsonParser = new JsonResponseParser();
895
+ resolveModel(tier) {
896
+ return resolveModel(this.tiers, tier, this.id);
897
+ }
898
+ async healthCheck() {
899
+ if (!this.config.apiKey) return "unavailable";
900
+ try {
901
+ const response = await this.transport.listModels();
902
+ if (response.ok) return "healthy";
903
+ if (response.status === 401 || response.status === 403) return "unavailable";
904
+ return "degraded";
905
+ } catch {
906
+ return "unavailable";
907
+ }
908
+ }
909
+ async estimateCost() {
910
+ return 0;
911
+ }
912
+ async generate(request) {
913
+ const model = request.model ?? this.resolveModel(request.tier ?? "balanced");
914
+ const wireRequest = new AnthropicRequestBuilder(model).withRequest(request).withEffort(this.config.effort).build();
915
+ const response = await this.transport.postMessages(wireRequest, {
916
+ signal: request.signal
917
+ });
918
+ return this.toGenerationResult(response, model);
919
+ }
920
+ async generateStructured(request) {
921
+ const model = request.model ?? this.resolveModel(request.tier ?? "balanced");
922
+ const messages = [...request.messages];
923
+ const lastUserIndex = findLastIndex(messages, (m) => m.role === "user");
924
+ if (lastUserIndex === -1) {
925
+ throw new AIError2(
926
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
927
+ "generateStructured requires at least one user message"
928
+ );
929
+ }
930
+ const structuredPrompt = new StructuredPromptBuilder(
931
+ messages[lastUserIndex].content
932
+ ).withSchema(request.schema).build();
933
+ messages[lastUserIndex] = { role: "user", content: structuredPrompt };
934
+ const wireRequest = new AnthropicRequestBuilder(model).withRequest({ ...request, messages }).withEffort(this.config.effort).build();
935
+ const response = await this.transport.postMessages(wireRequest, {
936
+ signal: request.signal
937
+ });
938
+ const result = this.toGenerationResult(response, model);
939
+ return { value: this.jsonParser.parse(result.text), result };
940
+ }
941
+ async stream(request, callbacks) {
942
+ const model = request.model ?? this.resolveModel(request.tier ?? "balanced");
943
+ const wireRequest = new AnthropicRequestBuilder(model).withRequest(request).withEffort(this.config.effort).withStream(true).build();
944
+ const streamBody = await this.transport.streamMessages(wireRequest, {
945
+ signal: request.signal
946
+ });
947
+ try {
948
+ const outcome = await consumeAnthropicStream(streamBody, callbacks);
949
+ const text = outcome.text.length > 0 ? outcome.text : refusalTextFor(outcome.finishReason);
950
+ const result = {
951
+ text,
952
+ usage: {
953
+ inputTokens: outcome.inputTokens ?? 0,
954
+ outputTokens: outcome.outputTokens ?? 0
955
+ },
956
+ model: outcome.model ?? model,
957
+ providerId: this.id,
958
+ finishReason: outcome.finishReason
959
+ };
960
+ callbacks.onComplete?.(result);
961
+ return result;
962
+ } catch (error) {
963
+ callbacks.onError?.(error instanceof Error ? error : new Error(String(error)));
964
+ throw error;
965
+ }
966
+ }
967
+ toGenerationResult(response, fallbackModel) {
968
+ const text = extractText(response.content);
969
+ const finishReason = mapAnthropicStopReason(response.stop_reason);
970
+ if (text.length === 0) {
971
+ if (finishReason === "refusal") {
972
+ return {
973
+ text: REFUSAL_FALLBACK_TEXT,
974
+ usage: mapAnthropicUsage(response.usage),
975
+ model: response.model || fallbackModel,
976
+ providerId: this.id,
977
+ finishReason
978
+ };
979
+ }
980
+ throw new AIError2(
981
+ "SERVER_ERROR" /* SERVER_ERROR */,
982
+ "No content generated from Anthropic API",
983
+ void 0,
984
+ { model: response.model || fallbackModel, stopReason: response.stop_reason ?? void 0 }
985
+ );
986
+ }
987
+ return {
988
+ text,
989
+ usage: mapAnthropicUsage(response.usage),
990
+ model: response.model || fallbackModel,
991
+ providerId: this.id,
992
+ finishReason
993
+ };
994
+ }
995
+ };
996
+ function extractText(blocks) {
997
+ return blocks.filter((block) => block.type === "text").map((block) => block.text).join("");
998
+ }
999
+ function refusalTextFor(finishReason) {
1000
+ return finishReason === "refusal" ? REFUSAL_FALLBACK_TEXT : "";
1001
+ }
1002
+ function mapAnthropicUsage(usage) {
1003
+ if (!usage) return void 0;
1004
+ return {
1005
+ inputTokens: usage.input_tokens,
1006
+ outputTokens: usage.output_tokens
1007
+ };
1008
+ }
1009
+ function findLastIndex(items, predicate) {
1010
+ for (let i = items.length - 1; i >= 0; i--) {
1011
+ if (predicate(items[i])) return i;
1012
+ }
1013
+ return -1;
1014
+ }
1015
+
1016
+ // src/core/calculations/lru-key-selector.ts
1017
+ function selectLeastRecentlyUsedKey(entries) {
1018
+ let oldestKey = null;
1019
+ let oldestAccess = Number.POSITIVE_INFINITY;
1020
+ for (const [key, entry] of entries.entries()) {
1021
+ if (entry.lastAccess < oldestAccess) {
1022
+ oldestAccess = entry.lastAccess;
1023
+ oldestKey = key;
1024
+ }
1025
+ }
1026
+ return oldestKey;
1027
+ }
1028
+
1029
+ // src/core/calculations/cache-hit-rate.ts
1030
+ function calculateCacheHitRate(entries) {
1031
+ if (entries.length === 0) return 0;
1032
+ const totalAccess = entries.reduce((sum, e) => sum + e.accessCount, 0);
1033
+ return totalAccess / entries.length;
1034
+ }
1035
+
1036
+ // src/core/utils/cache-key-builder.ts
1037
+ function buildCacheKey(params) {
1038
+ const sortedKeys = Object.keys(params).sort();
1039
+ const sortedParams = {};
1040
+ for (const key of sortedKeys) {
1041
+ sortedParams[key] = params[key];
1042
+ }
1043
+ return JSON.stringify(sortedParams);
1044
+ }
1045
+
1046
+ // src/core/utils/cache-manager.ts
1047
+ var DEFAULT_CACHE_MAX_SIZE = 100;
1048
+ var DEFAULT_CACHE_TTL_MS = 3e5;
1049
+ var CacheManager = class {
1050
+ cache = /* @__PURE__ */ new Map();
1051
+ maxSize;
1052
+ ttl;
1053
+ constructor(options = {}) {
1054
+ this.maxSize = options.maxSize ?? DEFAULT_CACHE_MAX_SIZE;
1055
+ this.ttl = options.ttl ?? DEFAULT_CACHE_TTL_MS;
1056
+ }
1057
+ get(key) {
1058
+ const entry = this.cache.get(key);
1059
+ if (!entry) return null;
1060
+ if (this.isExpired(entry)) {
1061
+ this.cache.delete(key);
1062
+ return null;
1063
+ }
1064
+ entry.accessCount += 1;
1065
+ entry.lastAccess = Date.now();
1066
+ return entry.value;
1067
+ }
1068
+ set(key, value) {
1069
+ if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
1070
+ const lruKey = selectLeastRecentlyUsedKey(this.cache);
1071
+ if (lruKey !== null) this.cache.delete(lruKey);
1072
+ }
1073
+ this.cache.set(key, {
1074
+ value,
1075
+ timestamp: Date.now(),
1076
+ accessCount: 1,
1077
+ lastAccess: Date.now()
1078
+ });
1079
+ }
1080
+ has(key) {
1081
+ const entry = this.cache.get(key);
1082
+ return entry !== void 0 && !this.isExpired(entry);
1083
+ }
1084
+ delete(key) {
1085
+ return this.cache.delete(key);
1086
+ }
1087
+ clear() {
1088
+ this.cache.clear();
1089
+ }
1090
+ generateKey(params) {
1091
+ return buildCacheKey(params);
1092
+ }
1093
+ getStats() {
1094
+ const now = Date.now();
1095
+ const entries = Array.from(this.cache.entries()).map(([key, entry]) => ({
1096
+ key,
1097
+ accessCount: entry.accessCount,
1098
+ ageMs: now - entry.timestamp
1099
+ }));
1100
+ const hitRate = calculateCacheHitRate(
1101
+ Array.from(this.cache.values()).map((e) => ({ accessCount: e.accessCount }))
1102
+ );
1103
+ return {
1104
+ size: this.cache.size,
1105
+ maxSize: this.maxSize,
1106
+ hitRate,
1107
+ entries
1108
+ };
1109
+ }
1110
+ isExpired(entry) {
1111
+ return Date.now() - entry.timestamp > this.ttl;
1112
+ }
1113
+ };
1114
+ new CacheManager();
1115
+
1116
+ // src/core/calculations/exponential-backoff.ts
1117
+ function calculateExponentialBackoff(params) {
1118
+ const {
1119
+ attempt,
1120
+ initialDelay,
1121
+ maxDelay,
1122
+ backoffMultiplier,
1123
+ jitterFactor,
1124
+ random = Math.random
1125
+ } = params;
1126
+ const base = initialDelay * Math.pow(backoffMultiplier, Math.max(0, attempt - 1));
1127
+ const capped = Math.min(base, maxDelay);
1128
+ const jitterMultiplier = random() * 2 - 1;
1129
+ const jitter = capped * jitterFactor * jitterMultiplier;
1130
+ return Math.max(0, capped + jitter);
1131
+ }
1132
+
1133
+ // src/core/constants/identifier.constants.ts
1134
+ var QUEUE_PRIORITY_USER_REQUEST = 10;
1135
+ var DEFAULT_RETRY_MAX_ATTEMPTS = 3;
1136
+ var DEFAULT_RETRY_INITIAL_DELAY_MS = 1e3;
1137
+ var DEFAULT_RETRY_MAX_DELAY_MS = 1e4;
1138
+ var DEFAULT_RETRY_BACKOFF_MULTIPLIER = 2;
1139
+ var DEFAULT_RETRY_JITTER_FACTOR = 0.1;
1140
+
1141
+ // src/core/utils/retry.ts
1142
+ var RETRYABLE_MESSAGE_KEYWORDS = [
1143
+ "network",
1144
+ "timeout",
1145
+ "econnrefused",
1146
+ "enotfound",
1147
+ "etimedout"
1148
+ ];
1149
+ var RetryManager = class {
1150
+ defaults;
1151
+ constructor() {
1152
+ this.defaults = {
1153
+ maxAttempts: DEFAULT_RETRY_MAX_ATTEMPTS,
1154
+ initialDelay: DEFAULT_RETRY_INITIAL_DELAY_MS,
1155
+ maxDelay: DEFAULT_RETRY_MAX_DELAY_MS,
1156
+ backoffMultiplier: DEFAULT_RETRY_BACKOFF_MULTIPLIER,
1157
+ jitterFactor: DEFAULT_RETRY_JITTER_FACTOR
1158
+ };
1159
+ }
1160
+ async execute(fn, options = {}) {
1161
+ const maxAttempts = options.maxAttempts ?? this.defaults.maxAttempts;
1162
+ let lastError;
1163
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1164
+ try {
1165
+ return await fn();
1166
+ } catch (error) {
1167
+ lastError = error instanceof Error ? error : new Error(String(error));
1168
+ if (attempt >= maxAttempts || !this.shouldRetry(lastError, options)) {
1169
+ throw lastError;
1170
+ }
1171
+ const delay = calculateExponentialBackoff({
1172
+ attempt,
1173
+ initialDelay: options.initialDelay ?? this.defaults.initialDelay,
1174
+ maxDelay: options.maxDelay ?? this.defaults.maxDelay,
1175
+ backoffMultiplier: options.backoffMultiplier ?? this.defaults.backoffMultiplier,
1176
+ jitterFactor: options.jitterFactor ?? this.defaults.jitterFactor
1177
+ });
1178
+ await this.delay(delay);
1179
+ }
1180
+ }
1181
+ throw lastError ?? new Error("Retry loop exited without result");
1182
+ }
1183
+ shouldRetry(error, options) {
1184
+ if (options.shouldRetry) return options.shouldRetry(error);
1185
+ if (error instanceof AIError2) {
1186
+ return isRetryableErrorType(error.type, DEFAULT_ERROR_CLASSIFICATION_RULE);
1187
+ }
1188
+ const lower = error.message.toLowerCase();
1189
+ return RETRYABLE_MESSAGE_KEYWORDS.some((kw) => lower.includes(kw));
1190
+ }
1191
+ delay(ms) {
1192
+ return new Promise((resolve) => setTimeout(resolve, ms));
1193
+ }
1194
+ };
1195
+
1196
+ // src/core/utils/request-queue.util.ts
1197
+ var DEFAULT_MAX_CONCURRENT = 3;
1198
+ var DEFAULT_PRIORITY = 0;
1199
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
1200
+ var RequestQueue = class {
1201
+ queue = [];
1202
+ activeRequests = /* @__PURE__ */ new Set();
1203
+ maxConcurrent;
1204
+ defaultPriority;
1205
+ requestTimeout;
1206
+ requestIdCounter = 0;
1207
+ constructor(options = {}) {
1208
+ this.maxConcurrent = options.maxConcurrent ?? DEFAULT_MAX_CONCURRENT;
1209
+ this.defaultPriority = options.defaultPriority ?? DEFAULT_PRIORITY;
1210
+ this.requestTimeout = options.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT_MS;
1211
+ }
1212
+ add(requestFn, priority) {
1213
+ return new Promise((resolve, reject) => {
1214
+ const id = `req-${++this.requestIdCounter}`;
1215
+ const queuedRequest = {
1216
+ id,
1217
+ priority: priority ?? this.defaultPriority,
1218
+ requestFn,
1219
+ resolve,
1220
+ reject,
1221
+ timestamp: Date.now()
1222
+ };
1223
+ this.queue.push(queuedRequest);
1224
+ this.sortQueue();
1225
+ this.process();
1226
+ });
1227
+ }
1228
+ cancelAll() {
1229
+ for (const request of this.queue) {
1230
+ request.reject(new Error("Request was cancelled"));
1231
+ }
1232
+ this.queue = [];
1233
+ }
1234
+ sortQueue() {
1235
+ this.queue.sort((a, b) => {
1236
+ if (a.priority !== b.priority) {
1237
+ return b.priority - a.priority;
1238
+ }
1239
+ return a.timestamp - b.timestamp;
1240
+ });
1241
+ }
1242
+ process() {
1243
+ while (this.queue.length > 0 && this.activeRequests.size < this.maxConcurrent) {
1244
+ const request = this.queue.shift();
1245
+ if (!request) break;
1246
+ this.executeRequest(request);
1247
+ }
1248
+ }
1249
+ executeRequest(request) {
1250
+ this.activeRequests.add(request.id);
1251
+ const timeoutId = setTimeout(() => {
1252
+ this.activeRequests.delete(request.id);
1253
+ request.reject(new Error(`Request ${request.id} timed out`));
1254
+ this.process();
1255
+ }, this.requestTimeout);
1256
+ request.requestFn().then((result) => {
1257
+ clearTimeout(timeoutId);
1258
+ request.resolve(result);
1259
+ }).catch((error) => {
1260
+ clearTimeout(timeoutId);
1261
+ request.reject(error);
1262
+ }).finally(() => {
1263
+ this.activeRequests.delete(request.id);
1264
+ this.process();
1265
+ });
1266
+ }
1267
+ };
1268
+ new RequestQueue();
1269
+
1270
+ // src/core/utils/request-deduplicator.util.ts
1271
+ var DEFAULT_DEDUPE_TTL_MS = 3e4;
1272
+ var RequestDeduplicator = class {
1273
+ pendingRequests = /* @__PURE__ */ new Map();
1274
+ ttl;
1275
+ constructor(ttl = DEFAULT_DEDUPE_TTL_MS) {
1276
+ this.ttl = ttl;
1277
+ }
1278
+ execute(key, requestFn) {
1279
+ this.cleanup();
1280
+ const existing = this.pendingRequests.get(key);
1281
+ if (existing) {
1282
+ return existing.promise;
1283
+ }
1284
+ const promise = requestFn();
1285
+ this.pendingRequests.set(key, {
1286
+ promise,
1287
+ timestamp: Date.now()
1288
+ });
1289
+ promise.catch(() => void 0).then(() => {
1290
+ this.pendingRequests.delete(key);
1291
+ });
1292
+ return promise;
1293
+ }
1294
+ cancelAll() {
1295
+ this.pendingRequests.clear();
1296
+ }
1297
+ cleanup() {
1298
+ const now = Date.now();
1299
+ for (const [key, request] of this.pendingRequests.entries()) {
1300
+ if (now - request.timestamp > this.ttl) {
1301
+ this.pendingRequests.delete(key);
1302
+ }
1303
+ }
1304
+ }
1305
+ };
1306
+
1307
+ // src/providers/groq/groq.constants.ts
1308
+ var GROQ_MODELS = {
1309
+ LLAMA_3_1_8B_INSTANT: "llama-3.1-8b-instant",
1310
+ LLAMA_3_3_70B_VERSATILE: "llama-3.3-70b-versatile",
1311
+ GPT_OSS_120B: "openai/gpt-oss-120b"};
1312
+ var GROQ_TIERS = {
1313
+ fast: GROQ_MODELS.LLAMA_3_1_8B_INSTANT,
1314
+ balanced: GROQ_MODELS.LLAMA_3_3_70B_VERSATILE,
1315
+ powerful: GROQ_MODELS.GPT_OSS_120B
1316
+ };
1317
+ var DEFAULT_GENERATION_CONFIG = {
1318
+ temperature: 0.7,
1319
+ maxTokens: 1024,
1320
+ topP: 1
1321
+ };
1322
+ var GROQ_BASE_URL = "https://api.groq.com/openai/v1";
1323
+ var CHAT_COMPLETIONS_PATH = "/chat/completions";
1324
+ var GROQ_TIMEOUTS = {
1325
+ DEFAULT: 3e4,
1326
+ STREAMING: 6e4
1327
+ };
1328
+ var HTTP_HEADERS = {
1329
+ CONTENT_TYPE: "Content-Type",
1330
+ APPLICATION_JSON: "application/json",
1331
+ AUTHORIZATION: "Authorization",
1332
+ BEARER_PREFIX: "Bearer "
1333
+ };
1334
+
1335
+ // src/providers/groq/groq-request.builder.ts
1336
+ var GroqChatRequestBuilder = class _GroqChatRequestBuilder {
1337
+ model;
1338
+ messages = [];
1339
+ generationConfig = {};
1340
+ streamEnabled = false;
1341
+ jsonResponse = false;
1342
+ constructor(model) {
1343
+ this.model = model;
1344
+ }
1345
+ static for(model) {
1346
+ return new _GroqChatRequestBuilder(model);
1347
+ }
1348
+ withMessages(messages) {
1349
+ this.messages = [...messages];
1350
+ return this;
1351
+ }
1352
+ withConfig(config) {
1353
+ this.generationConfig = config ? { ...config } : {};
1354
+ return this;
1355
+ }
1356
+ withStream(stream) {
1357
+ this.streamEnabled = stream;
1358
+ return this;
1359
+ }
1360
+ withJsonResponse(enabled) {
1361
+ this.jsonResponse = enabled;
1362
+ return this;
1363
+ }
1364
+ build() {
1365
+ const cfg = this.generationConfig;
1366
+ const request = {
1367
+ model: this.model,
1368
+ messages: this.messages,
1369
+ temperature: cfg.temperature ?? DEFAULT_GENERATION_CONFIG.temperature,
1370
+ max_tokens: cfg.maxTokens ?? DEFAULT_GENERATION_CONFIG.maxTokens,
1371
+ top_p: cfg.topP ?? DEFAULT_GENERATION_CONFIG.topP
1372
+ };
1373
+ if (cfg.n !== void 0) request.n = cfg.n;
1374
+ if (cfg.stop !== void 0) request.stop = cfg.stop;
1375
+ if (cfg.frequencyPenalty !== void 0) request.frequency_penalty = cfg.frequencyPenalty;
1376
+ if (cfg.presencePenalty !== void 0) request.presence_penalty = cfg.presencePenalty;
1377
+ if (cfg.reasoningEffort !== void 0) request.reasoning_effort = cfg.reasoningEffort;
1378
+ if (this.jsonResponse) {
1379
+ request.response_format = { type: "json_object" };
1380
+ }
1381
+ if (this.streamEnabled) request.stream = true;
1382
+ return request;
1383
+ }
1384
+ };
1385
+
1386
+ // src/providers/groq/groq-http-transport.ts
1387
+ var GroqHttpTransport = class {
1388
+ constructor(config) {
1389
+ this.config = config;
1390
+ }
1391
+ config;
1392
+ pendingAbortControllers = /* @__PURE__ */ new Set();
1393
+ cancelPendingRequests() {
1394
+ for (const controller of this.pendingAbortControllers) {
1395
+ controller.abort();
1396
+ }
1397
+ this.pendingAbortControllers.clear();
1398
+ }
1399
+ async postChatCompletion(request) {
1400
+ return this.request(CHAT_COMPLETIONS_PATH, {
1401
+ ...request,
1402
+ stream: false
1403
+ });
1404
+ }
1405
+ async streamChatCompletion(request, options = {}) {
1406
+ const url = `${this.baseUrl()}${CHAT_COMPLETIONS_PATH}`;
1407
+ const timeout = this.config.timeoutMs ?? GROQ_TIMEOUTS.STREAMING;
1408
+ const controller = new AbortController();
1409
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
1410
+ const onExternalAbort = () => controller.abort();
1411
+ options.signal?.addEventListener("abort", onExternalAbort);
1412
+ this.pendingAbortControllers.add(controller);
1413
+ try {
1414
+ const response = await fetch(url, {
1415
+ method: "POST",
1416
+ headers: this.buildHeaders(),
1417
+ body: JSON.stringify({ ...request, stream: true }),
1418
+ signal: controller.signal
1419
+ });
1420
+ if (!response.ok) await this.throwFromErrorResponse(response);
1421
+ if (!response.body) {
1422
+ throw new AIError2("NETWORK_ERROR" /* NETWORK_ERROR */, "Response body is null");
1423
+ }
1424
+ return response.body;
1425
+ } catch (error) {
1426
+ options.signal?.removeEventListener("abort", onExternalAbort);
1427
+ throw this.normalizeError(error);
1428
+ } finally {
1429
+ clearTimeout(timeoutId);
1430
+ this.pendingAbortControllers.delete(controller);
1431
+ }
1432
+ }
1433
+ baseUrl() {
1434
+ return this.config.baseUrl ?? GROQ_BASE_URL;
1435
+ }
1436
+ async request(endpoint, body) {
1437
+ const url = `${this.baseUrl()}${endpoint}`;
1438
+ const timeout = this.config.timeoutMs ?? GROQ_TIMEOUTS.DEFAULT;
1439
+ const controller = new AbortController();
1440
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
1441
+ this.pendingAbortControllers.add(controller);
1442
+ try {
1443
+ const response = await fetch(url, {
1444
+ method: "POST",
1445
+ headers: this.buildHeaders(),
1446
+ body: JSON.stringify(body),
1447
+ signal: controller.signal
1448
+ });
1449
+ if (!response.ok) await this.throwFromErrorResponse(response);
1450
+ return await response.json();
1451
+ } catch (error) {
1452
+ throw this.normalizeError(error);
1453
+ } finally {
1454
+ clearTimeout(timeoutId);
1455
+ this.pendingAbortControllers.delete(controller);
1456
+ }
1457
+ }
1458
+ buildHeaders() {
1459
+ return {
1460
+ [HTTP_HEADERS.CONTENT_TYPE]: HTTP_HEADERS.APPLICATION_JSON,
1461
+ [HTTP_HEADERS.AUTHORIZATION]: `${HTTP_HEADERS.BEARER_PREFIX}${this.config.apiKey}`
1462
+ };
1463
+ }
1464
+ async throwFromErrorResponse(response) {
1465
+ const errorMessage = await extractErrorMessageFromResponse(response);
1466
+ const metadata = {
1467
+ status: response.status,
1468
+ url: response.url
1469
+ };
1470
+ throw new AIError2(
1471
+ classifyHttpStatus(response.status),
1472
+ errorMessage,
1473
+ void 0,
1474
+ metadata
1475
+ );
1476
+ }
1477
+ normalizeError(error) {
1478
+ if (error instanceof AIError2) return error;
1479
+ if (error instanceof Error) {
1480
+ if (error.name === "AbortError") {
1481
+ return new AIError2(
1482
+ "ABORT_ERROR" /* ABORT_ERROR */,
1483
+ "Request was aborted by the client",
1484
+ error
1485
+ );
1486
+ }
1487
+ if (error.name === "TypeError" && error.message.toLowerCase().includes("network")) {
1488
+ return new AIError2(
1489
+ "NETWORK_ERROR" /* NETWORK_ERROR */,
1490
+ "Network error: Unable to connect to the AI service",
1491
+ error
1492
+ );
1493
+ }
1494
+ }
1495
+ return new AIError2(
1496
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
1497
+ error instanceof Error ? error.message : "Unknown error occurred",
1498
+ error instanceof Error ? error : void 0
1499
+ );
1500
+ }
1501
+ };
1502
+
1503
+ // src/core/executor/retry-executor.strategy.ts
1504
+ var RetryExecutor = class {
1505
+ constructor(retryManager2, options) {
1506
+ this.retryManager = retryManager2;
1507
+ this.options = options;
1508
+ }
1509
+ retryManager;
1510
+ options;
1511
+ execute(fn, ...args) {
1512
+ return this.retryManager.execute(() => fn(...args), this.options);
1513
+ }
1514
+ };
1515
+
1516
+ // src/core/executor/queue-executor.strategy.ts
1517
+ var QueueExecutor = class {
1518
+ constructor(queue, priority) {
1519
+ this.queue = queue;
1520
+ this.priority = priority;
1521
+ }
1522
+ queue;
1523
+ priority;
1524
+ execute(fn, ...args) {
1525
+ return this.queue.add(() => fn(...args), this.priority);
1526
+ }
1527
+ };
1528
+
1529
+ // src/core/executor/dedupe-executor.strategy.ts
1530
+ var DedupeExecutor = class {
1531
+ constructor(deduplicator, options) {
1532
+ this.deduplicator = deduplicator;
1533
+ this.options = options;
1534
+ }
1535
+ deduplicator;
1536
+ options;
1537
+ execute(fn, ...args) {
1538
+ const key = this.options.generateKey(...args);
1539
+ return this.deduplicator.execute(key, () => fn(...args));
1540
+ }
1541
+ };
1542
+
1543
+ // src/core/executor/composite-executor.strategy.ts
1544
+ var CompositeExecutor = class {
1545
+ executors;
1546
+ constructor(executors) {
1547
+ this.executors = executors;
1548
+ }
1549
+ execute(fn, ...args) {
1550
+ const composed = this.compress(fn);
1551
+ return composed(...args);
1552
+ }
1553
+ compress(fn) {
1554
+ return this.executors.reduceRight(
1555
+ (innerFn, executor) => (...a) => executor.execute(innerFn, ...a),
1556
+ fn
1557
+ );
1558
+ }
1559
+ };
1560
+
1561
+ // src/providers/groq/groq-http-client.ts
1562
+ var GroqHttpClient = class {
1563
+ transport;
1564
+ executor;
1565
+ constructor(config, deps = {}) {
1566
+ this.transport = deps.transport ?? new GroqHttpTransport(config);
1567
+ const retryManager2 = deps.retryManager ?? new RetryManager();
1568
+ const queue = deps.queue ?? new RequestQueue();
1569
+ const deduplicator = deps.deduplicator ?? new RequestDeduplicator();
1570
+ this.executor = new CompositeExecutor([
1571
+ new DedupeExecutor(deduplicator, {
1572
+ generateKey: (request) => deduplicatorKey(request, config.apiKey)
1573
+ }),
1574
+ new RetryExecutor(retryManager2),
1575
+ new QueueExecutor(queue, QUEUE_PRIORITY_USER_REQUEST)
1576
+ ]);
1577
+ }
1578
+ cancelPendingRequests() {
1579
+ this.transport.cancelPendingRequests();
1580
+ }
1581
+ postChatCompletion(request) {
1582
+ return this.executor.execute(
1583
+ (req) => this.transport.postChatCompletion(req),
1584
+ request
1585
+ );
1586
+ }
1587
+ streamChatCompletion(request, options) {
1588
+ return this.transport.streamChatCompletion(request, options);
1589
+ }
1590
+ };
1591
+ function deduplicatorKey(request, apiKey) {
1592
+ const { model, messages, stream: _stream, ...rest } = request;
1593
+ const stable = JSON.stringify({
1594
+ apiKey,
1595
+ model,
1596
+ messages: messages.map((m) => ({ role: m.role, content: m.content })),
1597
+ ...rest
1598
+ });
1599
+ return stable;
1600
+ }
1601
+
1602
+ // src/providers/groq/groq-sse.ts
1603
+ function mapGroqFinishReason(reason) {
1604
+ if (reason === "stop") return "stop";
1605
+ if (reason === "length") return "length";
1606
+ if (reason === "content_filter") return "content_filter";
1607
+ return void 0;
1608
+ }
1609
+ function mapGroqUsage(usage) {
1610
+ if (!usage) return void 0;
1611
+ return {
1612
+ inputTokens: usage.prompt_tokens,
1613
+ outputTokens: usage.completion_tokens,
1614
+ totalTokens: usage.total_tokens
1615
+ };
1616
+ }
1617
+ async function consumeGroqStream(stream, callbacks) {
1618
+ const reader = stream.getReader();
1619
+ const decoder = new TextDecoder();
1620
+ const chunks = [];
1621
+ let buffer = "";
1622
+ let model = null;
1623
+ let finishReason;
1624
+ let usage;
1625
+ const consumeLine = (rawLine) => {
1626
+ const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
1627
+ const trimmed = line.trim();
1628
+ if (trimmed.length === 0 || !isSseDataLine(trimmed)) return;
1629
+ const payload = extractSseDataPayload(trimmed);
1630
+ if (isSseDoneMarker(payload)) return;
1631
+ let chunk;
1632
+ try {
1633
+ chunk = JSON.parse(payload);
1634
+ } catch {
1635
+ return;
1636
+ }
1637
+ if (chunk.model) model = chunk.model;
1638
+ const choice = chunk.choices?.[0];
1639
+ const delta = choice?.delta?.content;
1640
+ if (typeof delta === "string" && delta.length > 0) {
1641
+ chunks.push(delta);
1642
+ callbacks.onChunk?.(delta);
1643
+ }
1644
+ if (choice?.finish_reason) {
1645
+ finishReason = mapGroqFinishReason(choice.finish_reason);
1646
+ }
1647
+ const tailUsage = chunk.x_groq?.usage;
1648
+ if (tailUsage) {
1649
+ usage = mapGroqUsage(tailUsage);
1650
+ }
1651
+ const errorBody = chunk.error;
1652
+ if (errorBody) {
1653
+ throw new AIError2(
1654
+ "SERVER_ERROR" /* SERVER_ERROR */,
1655
+ errorBody.message || "Groq stream failed mid-flight"
1656
+ );
1657
+ }
1658
+ };
1659
+ try {
1660
+ while (true) {
1661
+ const { done, value } = await reader.read();
1662
+ if (done) break;
1663
+ buffer += decoder.decode(value, { stream: true });
1664
+ const lines = buffer.split("\n");
1665
+ buffer = lines.pop() ?? "";
1666
+ for (const line of lines) consumeLine(line);
1667
+ }
1668
+ buffer += decoder.decode();
1669
+ if (buffer.length > 0) consumeLine(buffer);
1670
+ } catch (error) {
1671
+ if (error instanceof AIError2) throw error;
1672
+ throw new AIError2(
1673
+ "NETWORK_ERROR" /* NETWORK_ERROR */,
1674
+ "Stream was interrupted",
1675
+ error instanceof Error ? error : void 0
1676
+ );
1677
+ } finally {
1678
+ reader.releaseLock();
1679
+ }
1680
+ return { text: chunks.join(""), model, finishReason, usage };
1681
+ }
1682
+
1683
+ // src/providers/groq/groq.provider.ts
1684
+ var CACHE_LAYOUT_VERSION = 2;
1685
+ var STRUCTURED_TEMPERATURE = 0.1;
1686
+ var GroqProvider = class {
1687
+ constructor(config) {
1688
+ this.config = config;
1689
+ this.client = new GroqHttpClient(config);
1690
+ this.tiers = mergeTierOverrides(GROQ_TIERS, config.models);
1691
+ }
1692
+ config;
1693
+ id = "groq";
1694
+ name = "Groq";
1695
+ capabilities = ["text"];
1696
+ client;
1697
+ tiers;
1698
+ cache = new CacheManager();
1699
+ jsonParser = new JsonResponseParser();
1700
+ resolveModel(tier) {
1701
+ return resolveModel(this.tiers, tier, this.id);
1702
+ }
1703
+ async healthCheck() {
1704
+ return this.config.apiKey ? "healthy" : "unavailable";
1705
+ }
1706
+ async estimateCost() {
1707
+ return 0;
1708
+ }
1709
+ async generate(request) {
1710
+ const model = request.model ?? this.resolveModel(request.tier ?? "balanced");
1711
+ const cacheKey = this.cache.generateKey({
1712
+ v: CACHE_LAYOUT_VERSION,
1713
+ provider: this.id,
1714
+ op: "generate",
1715
+ model,
1716
+ messages: request.messages,
1717
+ systemPrompt: request.systemPrompt ?? null,
1718
+ temperature: request.temperature ?? null,
1719
+ maxTokens: request.maxTokens ?? null,
1720
+ topP: request.topP ?? null
1721
+ });
1722
+ const cached = this.cache.get(cacheKey);
1723
+ if (cached) return cached;
1724
+ const response = await this.client.postChatCompletion(
1725
+ this.buildWireRequest(model, request)
1726
+ );
1727
+ const choice = response.choices?.[0];
1728
+ const text = choice?.message?.content ?? "";
1729
+ if (text.length === 0) {
1730
+ throw new AIError2(
1731
+ "SERVER_ERROR" /* SERVER_ERROR */,
1732
+ "No content generated from Groq API",
1733
+ void 0,
1734
+ { model }
1735
+ );
1736
+ }
1737
+ const result = {
1738
+ text,
1739
+ usage: mapGroqUsage(response.usage),
1740
+ model: response.model || model,
1741
+ providerId: this.id,
1742
+ finishReason: mapGroqFinishReason(choice?.finish_reason)
1743
+ };
1744
+ this.cache.set(cacheKey, result);
1745
+ return result;
1746
+ }
1747
+ async generateStructured(request) {
1748
+ const model = request.model ?? this.resolveModel(request.tier ?? "balanced");
1749
+ const messages = toGroqMessages(request.messages, request.systemPrompt);
1750
+ const lastUserIndex = findLastIndex2(messages, (m) => m.role === "user");
1751
+ if (lastUserIndex === -1) {
1752
+ throw new AIError2(
1753
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
1754
+ "generateStructured requires at least one user message"
1755
+ );
1756
+ }
1757
+ const structuredPrompt = new StructuredPromptBuilder(
1758
+ messages[lastUserIndex].content
1759
+ ).withSchema(request.schema).build();
1760
+ messages[lastUserIndex] = { role: "user", content: structuredPrompt };
1761
+ const wireRequest = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig(this.generationConfig(request, STRUCTURED_TEMPERATURE)).withJsonResponse(true).build();
1762
+ const response = await this.client.postChatCompletion(wireRequest);
1763
+ const choice = response.choices?.[0];
1764
+ const text = choice?.message?.content ?? "";
1765
+ if (text.length === 0) {
1766
+ throw new AIError2(
1767
+ "SERVER_ERROR" /* SERVER_ERROR */,
1768
+ "No content generated from Groq API",
1769
+ void 0,
1770
+ { model }
1771
+ );
1772
+ }
1773
+ return {
1774
+ value: this.jsonParser.parse(text),
1775
+ result: {
1776
+ text,
1777
+ usage: mapGroqUsage(response.usage),
1778
+ model: response.model || model,
1779
+ providerId: this.id,
1780
+ finishReason: mapGroqFinishReason(choice?.finish_reason)
1781
+ }
1782
+ };
1783
+ }
1784
+ async stream(request, callbacks) {
1785
+ const model = request.model ?? this.resolveModel(request.tier ?? "balanced");
1786
+ const streamBody = await this.client.streamChatCompletion(
1787
+ this.buildWireRequest(model, request),
1788
+ { signal: request.signal }
1789
+ );
1790
+ try {
1791
+ const outcome = await consumeGroqStream(streamBody, callbacks);
1792
+ if (outcome.text.length === 0) {
1793
+ throw new AIError2(
1794
+ "SERVER_ERROR" /* SERVER_ERROR */,
1795
+ "No content generated from Groq API",
1796
+ void 0,
1797
+ { model }
1798
+ );
1799
+ }
1800
+ const result = {
1801
+ text: outcome.text,
1802
+ usage: outcome.usage,
1803
+ model: outcome.model ?? model,
1804
+ providerId: this.id,
1805
+ finishReason: outcome.finishReason
1806
+ };
1807
+ callbacks.onComplete?.(result);
1808
+ return result;
1809
+ } catch (error) {
1810
+ callbacks.onError?.(error instanceof Error ? error : new Error(String(error)));
1811
+ throw error;
1812
+ }
1813
+ }
1814
+ getCacheStats() {
1815
+ return this.cache.getStats();
1816
+ }
1817
+ clearCache() {
1818
+ this.cache.clear();
1819
+ }
1820
+ cancelPendingRequests() {
1821
+ this.client.cancelPendingRequests();
1822
+ }
1823
+ buildWireRequest(model, request) {
1824
+ return GroqChatRequestBuilder.for(model).withMessages(toGroqMessages(request.messages, request.systemPrompt)).withConfig(this.generationConfig(request)).build();
1825
+ }
1826
+ generationConfig(request, temperatureOverride) {
1827
+ return {
1828
+ ...request.temperature !== void 0 ? { temperature: request.temperature } : {},
1829
+ ...temperatureOverride !== void 0 ? { temperature: temperatureOverride } : {},
1830
+ ...request.maxTokens !== void 0 ? { maxTokens: request.maxTokens } : {},
1831
+ ...request.topP !== void 0 ? { topP: request.topP } : {},
1832
+ ...request.stopSequences !== void 0 && request.stopSequences.length > 0 ? { stop: [...request.stopSequences] } : {}
1833
+ };
1834
+ }
1835
+ };
1836
+ function toGroqMessages(messages, systemPrompt) {
1837
+ const wire = [];
1838
+ if (systemPrompt && systemPrompt.length > 0) {
1839
+ wire.push({ role: "system", content: systemPrompt });
1840
+ }
1841
+ for (const message of messages) {
1842
+ if (message.role === "system") {
1843
+ wire.push({ role: "system", content: message.content });
1844
+ continue;
1845
+ }
1846
+ wire.push({ role: message.role, content: message.content });
1847
+ }
1848
+ return wire;
1849
+ }
1850
+ function findLastIndex2(items, predicate) {
1851
+ for (let i = items.length - 1; i >= 0; i--) {
1852
+ if (predicate(items[i])) return i;
1853
+ }
1854
+ return -1;
1855
+ }
1856
+
1857
+ // src/content/domain/calculations/RetryBackoffDelay.ts
1858
+ function calculateRetryBackoffDelayMs(attemptIndex) {
1859
+ return ProviderTimingConfig.RETRY_BACKOFF_BASE_MS * Math.pow(2, attemptIndex);
1860
+ }
1861
+
1862
+ // src/providers/base/base.provider.ts
1863
+ var BaseProvider = class {
1864
+ config;
1865
+ constructor(config) {
1866
+ this.config = config;
1867
+ }
1868
+ async healthCheck() {
1869
+ if (!this.config.apiKey) {
1870
+ return "unavailable";
1871
+ }
1872
+ return "healthy";
1873
+ }
1874
+ async estimateCost(_request) {
1875
+ return 0;
1876
+ }
1877
+ async fetchWithTimeout(url, options, timeout) {
1878
+ const effectiveTimeout = timeout ?? this.config.timeout ?? ProviderTimingConfig.DEFAULT_TIMEOUT_MS;
1879
+ const controller = new AbortController();
1880
+ const timeoutId = setTimeout(() => controller.abort(), effectiveTimeout);
1881
+ try {
1882
+ const response = await fetch(url, {
1883
+ ...options,
1884
+ signal: controller.signal
1885
+ });
1886
+ clearTimeout(timeoutId);
1887
+ return response;
1888
+ } catch (error) {
1889
+ clearTimeout(timeoutId);
1890
+ throw error;
1891
+ }
1892
+ }
1893
+ async withRetry(fn, maxAttempts) {
1894
+ const attempts = maxAttempts ?? this.config.retryAttempts ?? ProviderTimingConfig.DEFAULT_RETRY_ATTEMPTS;
1895
+ let lastError;
1896
+ for (let i = 0; i < attempts; i++) {
1897
+ try {
1898
+ return await fn();
1899
+ } catch (error) {
1900
+ lastError = error;
1901
+ if (i < attempts - 1) {
1902
+ await new Promise((resolve) => setTimeout(resolve, calculateRetryBackoffDelayMs(i)));
1903
+ }
1904
+ }
1905
+ }
1906
+ throw lastError || new Error("Retry attempts exhausted");
1907
+ }
1908
+ };
1909
+
1910
+ // src/content/domain/predicates/ProviderCostEstimator.ts
1911
+ var FAL_FIXED_COST = 1;
1912
+ var GEMINI_IMAGE_COST = 0.02;
1913
+ var GEMINI_TEXT_PER_1K_CHARS_COST = 1e-4;
1914
+ function estimateFalGenerationCost(_request) {
1915
+ return FAL_FIXED_COST;
1916
+ }
1917
+ function estimateGeminiGenerationCost(request) {
1918
+ if (request.type === "image") return GEMINI_IMAGE_COST;
1919
+ return GEMINI_TEXT_PER_1K_CHARS_COST;
1920
+ }
1921
+
1922
+ // src/content/domain/limits/ModelDefaults.ts
1923
+ var ModelDefaults = {
1924
+ FAL_IMAGE: "fal-ai/flux/schnell",
1925
+ FAL_VIDEO: "fal-ai/hunyuan-video/1",
1926
+ FAL_IMAGE_TO_VIDEO: "fal-ai/fast-animatediff/image-to-video",
1927
+ GEMINI_TEXT: "gemini-2.0-flash",
1928
+ GEMINI_IMAGE: "imagen-4.0-generate-001"};
1929
+
1930
+ // src/providers/gemini/gemini.provider.ts
1931
+ var GEMINI_TIERS = {
1932
+ fast: "gemini-2.0-flash-lite",
1933
+ balanced: ModelDefaults.GEMINI_TEXT,
1934
+ powerful: "gemini-2.5-pro"
1935
+ };
1936
+ var GeminiProvider = class extends BaseProvider {
1937
+ id = "gemini";
1938
+ name = "Google Gemini";
1939
+ capabilities = ["text", "image"];
1940
+ models;
1941
+ tiers;
1942
+ jsonParser = new JsonResponseParser();
1943
+ constructor(config) {
1944
+ super({
1945
+ apiKey: config.apiKey,
1946
+ baseUrl: config.baseUrl || "https://generativelanguage.googleapis.com/v1beta",
1947
+ timeout: config.timeout || ProviderTimingConfig.GEMINI_TIMEOUT_MS,
1948
+ retryAttempts: config.retryAttempts || ProviderTimingConfig.GEMINI_RETRY_ATTEMPTS
1949
+ });
1950
+ this.models = {
1951
+ text: ModelDefaults.GEMINI_TEXT,
1952
+ image: ModelDefaults.GEMINI_IMAGE
1953
+ };
1954
+ if (config.models) {
1955
+ this.models = { ...this.models, ...config.models };
1956
+ }
1957
+ this.tiers = mergeTierOverrides(
1958
+ GEMINI_TIERS,
1959
+ config.models ? { balanced: config.models.text } : void 0
1960
+ );
1961
+ }
1962
+ resolveModel(tier) {
1963
+ return resolveModel(this.tiers, tier, this.id);
1964
+ }
1965
+ async healthCheck() {
1966
+ try {
1967
+ if (!this.config.apiKey) {
1968
+ return "unavailable";
1969
+ }
1970
+ const response = await this.fetchWithTimeout(
1971
+ `${this.config.baseUrl}/models`,
1972
+ {
1973
+ method: "GET",
1974
+ headers: this.authHeaders()
1975
+ }
1976
+ );
1977
+ return response.ok ? "healthy" : "unavailable";
1978
+ } catch {
1979
+ return "unavailable";
1980
+ }
1981
+ }
1982
+ async estimateCost(request) {
1983
+ return estimateGeminiGenerationCost(request);
1984
+ }
1985
+ async generate(request) {
1986
+ try {
1987
+ const model = request.model ?? this.resolveModel(request.tier ?? "balanced");
1988
+ const response = await this.withRetry(async () => {
1989
+ return await this.fetchWithTimeout(
1990
+ `${this.config.baseUrl}/models/${model}:generateContent`,
1991
+ {
1992
+ method: "POST",
1993
+ headers: this.authHeaders(),
1994
+ body: JSON.stringify({
1995
+ systemInstruction: request.systemPrompt && request.systemPrompt.length > 0 ? { parts: [{ text: request.systemPrompt }] } : void 0,
1996
+ contents: toGeminiContents(request.messages),
1997
+ generationConfig: {
1998
+ temperature: request.temperature || 0.7,
1999
+ maxOutputTokens: request.maxTokens || 2048,
2000
+ topP: request.topP || 0.9
2001
+ }
2002
+ })
2003
+ }
2004
+ );
2005
+ });
2006
+ if (!response.ok) {
2007
+ throw new Error(`Gemini API error: ${response.statusText}`);
2008
+ }
2009
+ const data = await response.json();
2010
+ const text = data.candidates[0]?.content?.parts?.map((part) => part.text ?? "").join("") ?? "";
2011
+ if (text.length === 0) {
2012
+ throw new AIError2("SERVER_ERROR" /* SERVER_ERROR */, "No content generated from Gemini API", void 0, { model });
2013
+ }
2014
+ return {
2015
+ text,
2016
+ usage: data.usageMetadata ? {
2017
+ inputTokens: data.usageMetadata.promptTokenCount,
2018
+ outputTokens: data.usageMetadata.candidatesTokenCount,
2019
+ totalTokens: data.usageMetadata.totalTokenCount
2020
+ } : void 0,
2021
+ model,
2022
+ providerId: this.id,
2023
+ finishReason: mapGeminiFinishReason(data.candidates[0]?.finishReason)
2024
+ };
2025
+ } catch (error) {
2026
+ if (error instanceof AIError2) throw error;
2027
+ throw new AIError2(
2028
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
2029
+ `Gemini text generation failed: ${error instanceof Error ? error.message : "Unknown error"}`,
2030
+ error instanceof Error ? error : void 0
2031
+ );
2032
+ }
2033
+ }
2034
+ async generateStructured(request) {
2035
+ const messages = [...request.messages];
2036
+ const lastUserIndex = findLastIndex3(messages, (m) => m.role === "user");
2037
+ if (lastUserIndex === -1) {
2038
+ throw new AIError2(
2039
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
2040
+ "generateStructured requires at least one user message"
2041
+ );
2042
+ }
2043
+ messages[lastUserIndex] = {
2044
+ role: "user",
2045
+ content: new StructuredPromptBuilder(messages[lastUserIndex].content).withSchema(request.schema).build()
2046
+ };
2047
+ const result = await this.generate({ ...request, messages, temperature: 0.1 });
2048
+ return { value: this.jsonParser.parse(result.text), result };
2049
+ }
2050
+ async stream(request, callbacks) {
2051
+ const result = await this.generate(request);
2052
+ callbacks.onChunk?.(result.text);
2053
+ callbacks.onComplete?.(result);
2054
+ return result;
2055
+ }
2056
+ async generateImage(request) {
2057
+ try {
2058
+ const response = await this.withRetry(async () => {
2059
+ return await this.fetchWithTimeout(
2060
+ `${this.config.baseUrl}/models/${this.models.image}:generateContent`,
2061
+ {
2062
+ method: "POST",
2063
+ headers: this.authHeaders(),
2064
+ body: JSON.stringify({
2065
+ contents: [{
2066
+ parts: [{
2067
+ text: this.buildImagePrompt(request)
2068
+ }]
2069
+ }]
2070
+ })
2071
+ }
2072
+ );
2073
+ });
2074
+ if (!response.ok) {
2075
+ throw new Error(`Gemini API error: ${response.statusText}`);
2076
+ }
2077
+ const data = await response.json();
2078
+ const imageData = data.candidates[0]?.content?.parts[0]?.inlineData;
2079
+ if (!imageData) {
2080
+ throw new Error("No image data in response");
2081
+ }
2082
+ const url = `data:${imageData.mimeType};base64,${imageData.data}`;
2083
+ return {
2084
+ id: Date.now().toString(),
2085
+ type: "image",
2086
+ url,
2087
+ metadata: {
2088
+ provider: "gemini",
2089
+ model: this.models.image,
2090
+ mimeType: imageData.mimeType
2091
+ },
2092
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2093
+ };
2094
+ } catch (error) {
2095
+ throw new Error(`Gemini image generation failed: ${error instanceof Error ? error.message : "Unknown error"}`);
2096
+ }
2097
+ }
2098
+ async generateVideo(_request) {
2099
+ throw new Error("Gemini provider does not support video generation");
2100
+ }
2101
+ async imageToVideo(_request) {
2102
+ throw new Error("Gemini provider does not support image-to-video conversion");
2103
+ }
2104
+ async videoToVideo(_request) {
2105
+ throw new Error("Gemini provider does not support video-to-video conversion");
2106
+ }
2107
+ authHeaders() {
2108
+ return {
2109
+ "Content-Type": "application/json",
2110
+ "x-goog-api-key": this.config.apiKey
2111
+ };
2112
+ }
2113
+ buildImagePrompt(request) {
2114
+ let prompt = request.prompt;
2115
+ if (request.style) {
2116
+ const styles = {
2117
+ realistic: "photorealistic, highly detailed, 8k resolution",
2118
+ cartoon: "cartoon style, colorful, animated",
2119
+ abstract: "abstract art, modern, artistic",
2120
+ minimalist: "minimalist, clean, simple",
2121
+ vintage: "vintage style, retro, classic"
2122
+ };
2123
+ prompt += `, ${styles[request.style]}`;
2124
+ }
2125
+ if (request.quality === "ultra") {
2126
+ prompt += ", ultra high quality, masterpiece";
2127
+ }
2128
+ return prompt;
2129
+ }
2130
+ };
2131
+ function toGeminiContents(messages) {
2132
+ return messages.filter((message) => message.role !== "system" && message.content.length > 0).map((message) => ({
2133
+ role: message.role === "assistant" ? "model" : "user",
2134
+ parts: [{ text: message.content }]
2135
+ }));
2136
+ }
2137
+ function mapGeminiFinishReason(reason) {
2138
+ if (reason === "STOP") return "stop";
2139
+ if (reason === "MAX_TOKENS") return "length";
2140
+ if (reason === "SAFETY" || reason === "PROHIBITED_CONTENT") return "content_filter";
2141
+ return void 0;
2142
+ }
2143
+ function findLastIndex3(items, predicate) {
2144
+ for (let i = items.length - 1; i >= 0; i--) {
2145
+ if (predicate(items[i])) return i;
2146
+ }
2147
+ return -1;
2148
+ }
2149
+
2150
+ // src/content/domain/predicates/KeywordProminenceClassifier.ts
2151
+ var HIGH_DENSITY_THRESHOLD = 2;
2152
+ var MEDIUM_DENSITY_THRESHOLD = 1;
2153
+ function classifyKeywordProminence(densityPercent) {
2154
+ if (densityPercent > HIGH_DENSITY_THRESHOLD) return "high";
2155
+ if (densityPercent > MEDIUM_DENSITY_THRESHOLD) return "medium";
2156
+ return "low";
2157
+ }
2158
+ function isUnderutilizedKeyword(densityPercent) {
2159
+ return densityPercent < 1;
2160
+ }
2161
+
2162
+ // src/content/domain/types/index.ts
2163
+ var PLATFORM_SPECS = {
2164
+ twitter: {
2165
+ maxLength: 280,
2166
+ style: "concise, engaging, thread-friendly",
2167
+ emojiSupport: true,
2168
+ hashtagSupport: true,
2169
+ imageSupport: true,
2170
+ videoSupport: true,
2171
+ maxHashtags: 5
2172
+ },
2173
+ linkedin: {
2174
+ maxLength: 3e3,
2175
+ style: "professional, insightful, industry-focused",
2176
+ emojiSupport: true,
2177
+ hashtagSupport: true,
2178
+ imageSupport: true,
2179
+ videoSupport: true,
2180
+ maxHashtags: 5
2181
+ },
2182
+ instagram: {
2183
+ maxLength: 2200,
2184
+ style: "visual, engaging, emoji-rich",
2185
+ emojiSupport: true,
2186
+ hashtagSupport: true,
2187
+ imageSupport: true,
2188
+ videoSupport: true,
2189
+ maxHashtags: 30
2190
+ },
2191
+ threads: {
2192
+ maxLength: 500,
2193
+ style: "conversational, community-driven",
2194
+ emojiSupport: true,
2195
+ hashtagSupport: true,
2196
+ imageSupport: true,
2197
+ videoSupport: true,
2198
+ maxHashtags: 5
2199
+ },
2200
+ tiktok: {
2201
+ maxLength: 150,
2202
+ style: "catchy, trending, short-form",
2203
+ emojiSupport: true,
2204
+ hashtagSupport: true,
2205
+ imageSupport: false,
2206
+ videoSupport: true,
2207
+ maxHashtags: 5
2208
+ },
2209
+ facebook: {
2210
+ maxLength: 63206,
2211
+ style: "conversational, engaging, shareable",
2212
+ emojiSupport: true,
2213
+ hashtagSupport: true,
2214
+ imageSupport: true,
2215
+ videoSupport: true,
2216
+ maxHashtags: 5
2217
+ }
2218
+ };
2219
+
2220
+ // src/content/domain/predicates/HashtagPriorityRanker.ts
2221
+ var DEFAULT_HASHTAG_BUDGET = Number.MAX_SAFE_INTEGER;
2222
+ function resolveHashtagBudget(platform) {
2223
+ return PLATFORM_SPECS[platform]?.maxHashtags ?? DEFAULT_HASHTAG_BUDGET;
2224
+ }
2225
+ function rankAndTrimHashtags(hashtags, budget) {
2226
+ if (hashtags.length <= budget) return [...hashtags];
2227
+ return [...hashtags].sort((a, b) => b.length - a.length).slice(0, budget);
2228
+ }
2229
+
2230
+ // src/content/domain/predicates/PlatformSpecResolver.ts
2231
+ var FALLBACK_SPEC = {
2232
+ maxLength: 280,
2233
+ style: "concise, engaging, audience-friendly",
2234
+ emojiSupport: true,
2235
+ hashtagSupport: true,
2236
+ imageSupport: true,
2237
+ videoSupport: true,
2238
+ maxHashtags: 5
2239
+ };
2240
+ function resolvePlatformSpec(platform) {
2241
+ return PLATFORM_SPECS[platform] ?? FALLBACK_SPEC;
2242
+ }
2243
+ function resolveMaxContentLength(platform, override) {
2244
+ return override ?? resolvePlatformSpec(platform).maxLength;
2245
+ }
2246
+
2247
+ // src/content/domain/predicates/ScoreRangeGuard.ts
2248
+ var SCORE_MIN = 0;
2249
+ var SCORE_MAX = 100;
2250
+ var SCORE_FALLBACK = 50;
2251
+ var CONFIDENCE_MIN = 0;
2252
+ var CONFIDENCE_MAX = 1;
2253
+ var CONFIDENCE_FALLBACK = 0.5;
2254
+ function clampScoreToValidRange(score, fallback = SCORE_FALLBACK) {
2255
+ if (!Number.isFinite(score)) return fallback;
2256
+ return Math.max(SCORE_MIN, Math.min(SCORE_MAX, score));
2257
+ }
2258
+ function clampConfidenceToValidRange(confidence, fallback = CONFIDENCE_FALLBACK) {
2259
+ if (!Number.isFinite(confidence)) return fallback;
2260
+ return Math.max(CONFIDENCE_MIN, Math.min(CONFIDENCE_MAX, confidence));
2261
+ }
2262
+
2263
+ // src/content/domain/predicates/AbTestConfidence.ts
2264
+ var AB_TEST_DEFAULT_CONFIDENCE = 0.7;
2265
+
2266
+ // src/content/domain/predicates/FalImageRenderingOptions.ts
2267
+ var FAL_FORMAT_TO_IMAGE_SIZE = {
2268
+ square: "square_hd",
2269
+ landscape: "landscape_16_9",
2270
+ portrait: "portrait_9_16",
2271
+ story: "portrait_9_16",
2272
+ banner: "landscape_16_9"
2273
+ };
2274
+ var FAL_DEFAULT_IMAGE_SIZE = "square_hd";
2275
+ var FAL_QUALITY_TO_STEPS = {
2276
+ standard: 4,
2277
+ high: 6,
2278
+ ultra: 8
2279
+ };
2280
+ var FAL_DEFAULT_QUALITY_STEPS = 6;
2281
+ function resolveFalImageSize(format) {
2282
+ if (!format) return FAL_DEFAULT_IMAGE_SIZE;
2283
+ return FAL_FORMAT_TO_IMAGE_SIZE[format] ?? FAL_DEFAULT_IMAGE_SIZE;
2284
+ }
2285
+ function resolveFalInferenceSteps(quality) {
2286
+ if (!quality) return FAL_DEFAULT_QUALITY_STEPS;
2287
+ return FAL_QUALITY_TO_STEPS[quality] ?? FAL_DEFAULT_QUALITY_STEPS;
2288
+ }
2289
+
2290
+ // src/providers/fal/fal.provider.ts
2291
+ var FalProvider = class extends BaseProvider {
2292
+ id = "fal";
2293
+ name = "FAL";
2294
+ capabilities = ["image", "video"];
2295
+ models;
2296
+ constructor(config) {
2297
+ super({
2298
+ apiKey: config.apiKey,
2299
+ baseUrl: config.baseUrl || "https://queue.fal.run",
2300
+ timeout: config.timeout || ProviderTimingConfig.FAL_TIMEOUT_MS,
2301
+ retryAttempts: config.retryAttempts || ProviderTimingConfig.FAL_RETRY_ATTEMPTS
2302
+ });
2303
+ this.models = {
2304
+ image: ModelDefaults.FAL_IMAGE,
2305
+ video: ModelDefaults.FAL_VIDEO,
2306
+ imageToVideo: ModelDefaults.FAL_IMAGE_TO_VIDEO
2307
+ };
2308
+ if (config.models) {
2309
+ this.models = { ...this.models, ...config.models };
2310
+ }
2311
+ }
2312
+ async healthCheck() {
2313
+ try {
2314
+ if (!this.config.apiKey) {
2315
+ return "unavailable";
2316
+ }
2317
+ const response = await this.fetchWithTimeout(
2318
+ `${this.config.baseUrl}/fal-ai/flux/schnell/status`,
2319
+ {
2320
+ method: "HEAD",
2321
+ headers: {
2322
+ "Authorization": `Key ${this.config.apiKey}`
2323
+ }
2324
+ }
2325
+ );
2326
+ return response.ok ? "healthy" : "degraded";
2327
+ } catch {
2328
+ return "unavailable";
2329
+ }
2330
+ }
2331
+ async estimateCost(request) {
2332
+ return estimateFalGenerationCost();
2333
+ }
2334
+ async generateImage(request) {
2335
+ try {
2336
+ const response = await this.withRetry(async () => {
2337
+ return await this.fetchWithTimeout(
2338
+ `${this.config.baseUrl}/${this.models.image}`,
2339
+ {
2340
+ method: "POST",
2341
+ headers: {
2342
+ "Authorization": `Key ${this.config.apiKey}`,
2343
+ "Content-Type": "application/json"
2344
+ },
2345
+ body: JSON.stringify({
2346
+ prompt: request.prompt,
2347
+ image_size: resolveFalImageSize(request.format),
2348
+ num_inference_steps: resolveFalInferenceSteps(request.quality),
2349
+ num_images: request.quantity || 1
2350
+ })
2351
+ }
2352
+ );
2353
+ });
2354
+ if (!response.ok) {
2355
+ throw new Error(`FAL API error: ${response.statusText}`);
2356
+ }
2357
+ const data = await response.json();
2358
+ if (data.error) {
2359
+ throw new Error(data.error);
2360
+ }
2361
+ return {
2362
+ id: Date.now().toString(),
2363
+ type: "image",
2364
+ url: data.images?.[0]?.url,
2365
+ metadata: {
2366
+ provider: "fal",
2367
+ model: this.models.image
2368
+ },
2369
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2370
+ };
2371
+ } catch (error) {
2372
+ throw new Error(`FAL image generation failed: ${error instanceof Error ? error.message : "Unknown error"}`);
2373
+ }
2374
+ }
2375
+ async generateVideo(request) {
2376
+ try {
2377
+ const response = await this.withRetry(async () => {
2378
+ return await this.fetchWithTimeout(
2379
+ `${this.config.baseUrl}/${this.models.video}`,
2380
+ {
2381
+ method: "POST",
2382
+ headers: {
2383
+ "Authorization": `Key ${this.config.apiKey}`,
2384
+ "Content-Type": "application/json"
2385
+ },
2386
+ body: JSON.stringify({
2387
+ prompt: request.prompt,
2388
+ aspect_ratio: request.aspectRatio || "16:9"
2389
+ })
2390
+ }
2391
+ );
2392
+ });
2393
+ if (!response.ok) {
2394
+ throw new Error(`FAL API error: ${response.statusText}`);
2395
+ }
2396
+ const data = await response.json();
2397
+ if (data.error) {
2398
+ throw new Error(data.error);
2399
+ }
2400
+ return {
2401
+ id: Date.now().toString(),
2402
+ type: "video",
2403
+ url: data.video?.url,
2404
+ metadata: {
2405
+ provider: "fal",
2406
+ model: this.models.video
2407
+ },
2408
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2409
+ };
2410
+ } catch (error) {
2411
+ throw new Error(`FAL video generation failed: ${error instanceof Error ? error.message : "Unknown error"}`);
2412
+ }
2413
+ }
2414
+ async imageToVideo(request) {
2415
+ try {
2416
+ const response = await this.withRetry(async () => {
2417
+ return await this.fetchWithTimeout(
2418
+ `${this.config.baseUrl}/${this.models.imageToVideo}`,
2419
+ {
2420
+ method: "POST",
2421
+ headers: {
2422
+ "Authorization": `Key ${this.config.apiKey}`,
2423
+ "Content-Type": "application/json"
2424
+ },
2425
+ body: JSON.stringify({
2426
+ image_url: request.imageUrl,
2427
+ motion: request.motion || "medium",
2428
+ prompt: request.prompt
2429
+ })
2430
+ }
2431
+ );
2432
+ });
2433
+ if (!response.ok) {
2434
+ throw new Error(`FAL API error: ${response.statusText}`);
2435
+ }
2436
+ const data = await response.json();
2437
+ if (data.error) {
2438
+ throw new Error(data.error);
2439
+ }
2440
+ return {
2441
+ id: Date.now().toString(),
2442
+ type: "video",
2443
+ url: data.video?.url,
2444
+ metadata: {
2445
+ provider: "fal",
2446
+ model: this.models.imageToVideo
2447
+ },
2448
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2449
+ };
2450
+ } catch (error) {
2451
+ throw new Error(`FAL image-to-video failed: ${error instanceof Error ? error.message : "Unknown error"}`);
2452
+ }
2453
+ }
2454
+ async videoToVideo(_request) {
2455
+ throw new Error("FAL provider does not support video-to-video conversion");
2456
+ }
2457
+ };
2458
+
2459
+ // src/providers/pruna/pruna.provider.ts
2460
+ var PrunaProvider = class {
2461
+ constructor(config) {
2462
+ this.config = config;
2463
+ }
2464
+ config;
2465
+ id = "pruna";
2466
+ name = "Pruna AI";
2467
+ capabilities = ["image", "video"];
2468
+ async healthCheck() {
2469
+ return this.config.apiKey ? "healthy" : "unavailable";
2470
+ }
2471
+ async estimateCost(_request) {
2472
+ return 1;
2473
+ }
2474
+ async generateImage(_request) {
2475
+ throw new AIError2("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2476
+ }
2477
+ async generateVideo(_request) {
2478
+ throw new AIError2("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2479
+ }
2480
+ async imageToVideo(_request) {
2481
+ throw new AIError2("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2482
+ }
2483
+ async videoToVideo(_request) {
2484
+ throw new AIError2("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2485
+ }
2486
+ };
2487
+
2488
+ // src/providers/factory/create-ai.ts
2489
+ function createAI(config = {}) {
2490
+ const factory = new ProviderFactory({
2491
+ priority: config.priority,
2492
+ fallbackEnabled: config.fallbackEnabled,
2493
+ defaultTier: config.defaultTier
2494
+ });
2495
+ if (config.anthropic?.apiKey) {
2496
+ factory.register(new AnthropicProvider(config.anthropic));
2497
+ }
2498
+ if (config.groq?.apiKey) {
2499
+ factory.register(new GroqProvider(config.groq));
2500
+ }
2501
+ if (config.gemini?.apiKey) {
2502
+ factory.register(new GeminiProvider({ ...config.gemini, enabled: true }));
2503
+ }
2504
+ if (config.fal?.apiKey) {
2505
+ factory.register(new FalProvider({ ...config.fal, enabled: true }));
2506
+ }
2507
+ if (config.pruna?.apiKey) {
2508
+ factory.register(new PrunaProvider({ ...config.pruna, enabled: true }));
2509
+ }
2510
+ return factory;
2511
+ }
2512
+
2513
+ // src/content/domain/limits/SpeechPaceConfig.ts
2514
+ var SpeechPaceConfig = {
2515
+ WORDS_PER_SECOND: 2.5,
2516
+ TOKENS_PER_SECOND: 10,
2517
+ JSON_TOKEN_OVERHEAD: 500,
2518
+ TOKENS_PER_CALENDAR_DAY: 150
2519
+ };
2520
+
2521
+ // src/content/domain/calculations/SpeechPaceEstimate.ts
2522
+ function estimateWordCountForDuration(durationSeconds) {
2523
+ return Math.floor(durationSeconds * SpeechPaceConfig.WORDS_PER_SECOND);
2524
+ }
2525
+ function estimateTokenBudgetForDuration(durationSeconds) {
2526
+ return durationSeconds * SpeechPaceConfig.TOKENS_PER_SECOND + SpeechPaceConfig.JSON_TOKEN_OVERHEAD;
2527
+ }
2528
+ function estimateTokenBudgetForCalendarDays(days) {
2529
+ return days * SpeechPaceConfig.TOKENS_PER_CALENDAR_DAY + SpeechPaceConfig.JSON_TOKEN_OVERHEAD;
2530
+ }
2531
+
2532
+ // src/content/application/prompts/PromptBuilder.ts
2533
+ var PromptBuilder = class {
2534
+ static blogPost(input) {
2535
+ return `
2536
+ As an expert SEO Content Strategist and Copywriter, generate a high-quality, comprehensive blog post.
2537
+
2538
+ TOPIC: ${input.topic}
2539
+ TYPE: ${input.blogType}
2540
+ KEYWORDS: ${input.keywords.join(", ")}
2541
+ TONE: ${input.tone}
2542
+ TARGET AUDIENCE: ${input.targetAudience}
2543
+ WORD COUNT: ${input.wordCount}
2544
+ LANGUAGE: ${input.language || "English"}
2545
+
2546
+ OPTIMIZATION REQUIREMENTS:
2547
+ - SEO Optimization: ${input.seoOptimization ? "ON (Include semantic keywords, LSI, and proper H1-H3 structure)" : "OFF"}
2548
+ - Include Schema Markup: ${input.includeSchema ? "ON (Generate JSON-LD for this blog type)" : "OFF"}
2549
+ - Image Suggestions: ${input.includeImages ? "ON (Provide DALL-E/Midjourney style prompts for contextually relevant images)" : "OFF"}
2550
+
2551
+ RESPONSE FORMAT (STRICT JSON ONLY):
2552
+ {
2553
+ "title": "Compelling, Click-worthy Title",
2554
+ "content": "Full markdown-formatted blog content with headers",
2555
+ "metaDescription": "SEO-optimized description (150-160 chars)",
2556
+ "seoScore": 0-100,
2557
+ "readabilityScore": 0-100,
2558
+ "keywords": ["list", "of", "optimized", "keywords"],
2559
+ "performance": {
2560
+ "readingTime": minutes (number),
2561
+ "shareability": 0-10 (number),
2562
+ "seoRank": 0-10 (number)
2563
+ },
2564
+ "schemaMarkup": "JSON-LD string (if requested)",
2565
+ "imagePrompts": ["Prompt 1", "Prompt 2"] (if requested)
2566
+ }
2567
+ `;
2568
+ }
2569
+ static socialPost(input) {
2570
+ return `
2571
+ Write a ${input.tone} social media post about: ${input.topic}
2572
+
2573
+ Platform: ${input.platform}
2574
+ Style: ${input.style}
2575
+ Maximum length: ${input.maxLength} characters
2576
+ ${input.includeHashtags ? "Include relevant hashtags" : "No hashtags"}
2577
+ ${input.includeCallToAction ? "Include a call-to-action" : ""}
2578
+
2579
+ Requirements:
2580
+ - Platform-optimized formatting
2581
+ - Engaging hook in first sentence
2582
+ - Natural, conversational tone
2583
+ - Platform-appropriate emoji usage
2584
+
2585
+ Generate the post in JSON format:
2586
+ {
2587
+ "content": "The post content",
2588
+ "hashtags": ["hashtag1", "hashtag2"],
2589
+ "emojis": ["emoji1", "emoji2"],
2590
+ "estimatedEngagement": 0-100
2591
+ }
2592
+ `;
2593
+ }
2594
+ static videoScript(input) {
2595
+ return `
2596
+ Create a ${input.tone} video script about: ${input.topic}
2597
+
2598
+ Duration: ${input.duration} seconds
2599
+ Target Audience: ${input.targetAudience}
2600
+ Include Visual Cues: ${input.includeVisuals ? "Yes" : "No"}
2601
+ Include CTA: ${input.includeCallToAction ? "Yes" : "No"}
2602
+
2603
+ Approximate word count: ${estimateWordCountForDuration(input.duration)} words
2604
+
2605
+ Generate in JSON format:
2606
+ {
2607
+ "script": "Full script with dialogue and narration",
2608
+ "visualCues": ["Visual cue 1", "Visual cue 2"],
2609
+ "callToAction": "Call to action text (if requested)",
2610
+ "estimatedEngagement": 0-100
2611
+ }
2612
+ `;
2613
+ }
2614
+ static voiceScript(input) {
2615
+ return `
2616
+ Create a ${input.emotion} voice script about: ${input.topic}
2617
+
2618
+ Duration: ${input.duration} seconds
2619
+ Emotion: ${input.emotion}
2620
+
2621
+ The script should:
2622
+ - Evoke the specified emotion
2623
+ - Be suitable for voice recording with emotion control
2624
+ - Include natural speech patterns
2625
+ - Have appropriate pacing for the emotion
2626
+ - Include emotional cues in brackets where needed
2627
+
2628
+ Approximate word count: ${estimateWordCountForDuration(input.duration)} words
2629
+
2630
+ Generate in JSON format:
2631
+ {
2632
+ "script": "Full script with emotional cues",
2633
+ "visualCues": ["Visual cue 1", "Visual cue 2"],
2634
+ "estimatedEngagement": 0-100
2635
+ }
2636
+ `;
2637
+ }
2638
+ static contentCalendar(niche, days) {
2639
+ return `
2640
+ Generate a ${days}-day content calendar for: ${niche}
2641
+
2642
+ For each day, provide:
2643
+ - Topic
2644
+ - Content type (blog, video, social, email, caption, script)
2645
+ - Best platform for this content
2646
+ - Brief description
2647
+ - Estimated engagement (0-100)
2648
+ - Priority level (high, medium, low)
2649
+
2650
+ Format as JSON array:
2651
+ [
2652
+ {
2653
+ "day": 1,
2654
+ "topic": "...",
2655
+ "contentType": "blog|video|social|email|caption|script",
2656
+ "platform": "twitter|linkedin|instagram|threads|tiktok|facebook",
2657
+ "description": "...",
2658
+ "estimatedEngagement": 0-100,
2659
+ "priority": "high|medium|low"
2660
+ }
2661
+ ]
2662
+
2663
+ Requirements:
2664
+ - Mix of content types across platforms
2665
+ - Platform-appropriate suggestions
2666
+ - Engaging, varied topics
2667
+ - Realistic posting schedule
2668
+ `;
2669
+ }
2670
+ static sentiment(content) {
2671
+ return `
2672
+ Analyze the sentiment of this social media post:
2673
+
2674
+ "${content}"
2675
+
2676
+ Provide analysis in JSON format:
2677
+ {
2678
+ "sentiment": "positive|neutral|negative",
2679
+ "confidence": 0-1,
2680
+ "emotions": [
2681
+ {"emotion": "happy|excited|calm|sad|angry|surprised|fearful|disgusted|neutral", "score": 0-1}
2682
+ ]
2683
+ }
2684
+
2685
+ Return only the JSON:
2686
+ `;
2687
+ }
2688
+ static contentAnalysis(content) {
2689
+ return `
2690
+ Analyze this content:
2691
+
2692
+ "${content}"
2693
+
2694
+ Provide analysis in JSON format:
2695
+ {
2696
+ "keywords": ["keyword1", "keyword2", ...],
2697
+ "entities": ["entity1", "entity2", ...],
2698
+ "suggestedImprovements": ["suggestion1", ...],
2699
+ "readabilityScore": 0-100,
2700
+ "estimatedEngagement": 0-100
2701
+ }
2702
+
2703
+ Return only the JSON:
2704
+ `;
2705
+ }
2706
+ static seoOptimization(content, keywords) {
2707
+ return `
2708
+ Optimize this content for SEO:
2709
+
2710
+ Content: "${content}"
2711
+ Keywords: ${keywords.join(", ")}
2712
+
2713
+ Tasks:
2714
+ 1. Incorporate keywords naturally
2715
+ 2. Improve engagement potential
2716
+ 3. Add relevant call-to-action if appropriate
2717
+ 4. Optimize for readability
2718
+ 5. Enhance heading structure
2719
+
2720
+ Return JSON:
2721
+ {
2722
+ "optimized": "...",
2723
+ "score": 0-100,
2724
+ "suggestions": ["suggestion1", ...],
2725
+ "addedKeywords": ["keyword1", ...],
2726
+ "removedFillerWords": number,
2727
+ "readabilityImprovements": ["improvement1", ...],
2728
+ "metaDescription": "SEO-optimized meta description",
2729
+ "titleSuggestions": ["title1", ...]
2730
+ }
2731
+
2732
+ Return only the JSON:
2733
+ `;
2734
+ }
2735
+ static seoScore(content, keywords) {
2736
+ return `
2737
+ Calculate SEO score for this content:
2738
+
2739
+ Content: "${content}"
2740
+ Keywords: ${keywords.join(", ")}
2741
+
2742
+ Provide breakdown in JSON:
2743
+ {
2744
+ "keywordDensity": 0-100,
2745
+ "readabilityScore": 0-100,
2746
+ "titleOptimization": 0-100,
2747
+ "metaDescription": 0-100,
2748
+ "headingStructure": 0-100,
2749
+ "internalLinking": 0-100,
2750
+ "overall": 0-100
2751
+ }
2752
+
2753
+ Return only the JSON:
2754
+ `;
2755
+ }
2756
+ static abTestVariant(input) {
2757
+ return `
2758
+ Predict performance for this content variant:
2759
+
2760
+ Content: "${input.content}"
2761
+ Content Type: ${input.contentType}
2762
+ Tone: ${input.tone}
2763
+ Target Audience: ${input.targetAudience}
2764
+ Platform: ${input.platform}
2765
+ Goals: ${input.goals.join(", ")}
2766
+
2767
+ Provide prediction in JSON:
2768
+ {
2769
+ "predictedEngagement": 0-100,
2770
+ "predictedCTR": 0-100,
2771
+ "predictedConversions": 0-100,
2772
+ "confidence": 0-1,
2773
+ "reasoning": "...",
2774
+ "strengths": ["strength1", ...],
2775
+ "weaknesses": ["weakness1", ...],
2776
+ "suggestions": ["suggestion1", ...]
2777
+ }
2778
+
2779
+ Return only the JSON:
2780
+ `;
2781
+ }
2782
+ static hashtags(content, count) {
2783
+ return `
2784
+ Generate ${count} relevant hashtags for this content:
2785
+
2786
+ "${content}"
2787
+
2788
+ Requirements:
2789
+ - Mix of popular and niche hashtags
2790
+ - Industry-specific when possible
2791
+ - Trending if relevant
2792
+ - No duplicated words
2793
+ - Format: #hashtag (one per line)
2794
+
2795
+ Return only the hashtags:
2796
+ `;
2797
+ }
2798
+ static imagePrompt(description, style) {
2799
+ return `
2800
+ Generate a detailed AI image generation prompt for:
2801
+
2802
+ Description: ${description}
2803
+ Style: ${style || "realistic"}
2804
+
2805
+ Create a detailed, descriptive prompt that includes:
2806
+ - Subject details
2807
+ - Mood and atmosphere
2808
+ - Lighting
2809
+ - Composition
2810
+ - Art style reference
2811
+ - Technical specifications (aspect ratio, quality)
2812
+
2813
+ The prompt should be optimized for models like Midjourney, DALL-E, or Stable Diffusion.
2814
+
2815
+ Generate the prompt:
2816
+ `;
2817
+ }
2818
+ static blogImagePrompts(blogContent) {
2819
+ return `
2820
+ Analyze this blog content and generate 3-5 detailed image prompts for illustrative images:
2821
+
2822
+ Blog Content: "${blogContent}"
2823
+
2824
+ For each image prompt, provide:
2825
+ - Subject description
2826
+ - Mood and atmosphere
2827
+ - Style reference
2828
+ - Composition details
2829
+ - Technical specs
2830
+
2831
+ Return as JSON array:
2832
+ ["prompt1", "prompt2", "prompt3", ...]
2833
+
2834
+ Return only the JSON:
2835
+ `;
2836
+ }
2837
+ static maxTokensForDuration(durationSeconds) {
2838
+ return estimateTokenBudgetForDuration(durationSeconds);
2839
+ }
2840
+ };
2841
+
2842
+ // src/content/domain/limits/ValidationLimits.ts
2843
+ var ValidationLimits = {
2844
+ TOPIC: {
2845
+ MIN: 1,
2846
+ MAX: 500
2847
+ },
2848
+ CONTENT: {
2849
+ MIN: 1,
2850
+ MAX: 1e4
2851
+ },
2852
+ KEYWORD: {
2853
+ MAX_COUNT: 20,
2854
+ MAX_LENGTH: 50
2855
+ },
2856
+ DURATION_SECONDS: {
2857
+ MIN: 10,
2858
+ MAX: 600
2859
+ },
2860
+ CALENDAR_DAYS: {
2861
+ MIN: 1,
2862
+ MAX: 90
2863
+ },
2864
+ HASHTAG_COUNT: {
2865
+ MIN: 1,
2866
+ MAX: 50
2867
+ },
2868
+ SEO_CONTENT_PREVIEW: 500,
2869
+ BLOG_PROMPT_PREVIEW: 1e3,
2870
+ AB_VARIANT_CONTENT_PREVIEW: 200
2871
+ };
2872
+
2873
+ // src/content/domain/validation/TopicTextValidator.ts
2874
+ function validateTopicText(topic) {
2875
+ if (typeof topic !== "string" || topic.trim().length < ValidationLimits.TOPIC.MIN) {
2876
+ throw new ValidationError("Topic cannot be empty");
2877
+ }
2878
+ const trimmed = topic.trim();
2879
+ if (trimmed.length > ValidationLimits.TOPIC.MAX) {
2880
+ throw new ValidationError(`Topic exceeds maximum length of ${ValidationLimits.TOPIC.MAX} characters`);
2881
+ }
2882
+ return trimmed;
2883
+ }
2884
+
2885
+ // src/content/domain/validation/KeywordListValidator.ts
2886
+ function validateKeywordList(keywords) {
2887
+ if (!Array.isArray(keywords)) {
2888
+ throw new ValidationError("Keywords must be an array");
2889
+ }
2890
+ if (keywords.length > ValidationLimits.KEYWORD.MAX_COUNT) {
2891
+ throw new ValidationError(`Cannot process more than ${ValidationLimits.KEYWORD.MAX_COUNT} keywords`);
2892
+ }
2893
+ return keywords.filter((k) => typeof k === "string" && k.trim().length > 0).map((keyword) => {
2894
+ const trimmed = keyword.trim();
2895
+ if (trimmed.length > ValidationLimits.KEYWORD.MAX_LENGTH) {
2896
+ const preview = trimmed.substring(0, 20);
2897
+ throw new ValidationError(
2898
+ `Keyword "${preview}..." exceeds maximum length of ${ValidationLimits.KEYWORD.MAX_LENGTH} characters`
2899
+ );
2900
+ }
2901
+ return trimmed;
2902
+ });
2903
+ }
2904
+
2905
+ // src/content/domain/utils/IdGenerator.ts
2906
+ function generateId(prefix) {
2907
+ const id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
2908
+ return prefix ? `${prefix}-${id}` : id;
2909
+ }
2910
+
2911
+ // src/content/application/services/BlogService.ts
2912
+ var BlogService = class {
2913
+ constructor(executor) {
2914
+ this.executor = executor;
2915
+ }
2916
+ executor;
2917
+ async generateBlogPost(request) {
2918
+ const topic = validateTopicText(request.topic);
2919
+ const keywords = validateKeywordList(request.targetKeywords);
2920
+ const prompt = PromptBuilder.blogPost({
2921
+ topic,
2922
+ blogType: request.blogType,
2923
+ keywords,
2924
+ tone: request.tone,
2925
+ targetAudience: request.targetAudience,
2926
+ wordCount: request.wordCount,
2927
+ language: request.language,
2928
+ seoOptimization: request.seoOptimization,
2929
+ includeSchema: request.includeSchema,
2930
+ includeImages: request.includeImages
2931
+ });
2932
+ const parsed = await this.executor.runJson(
2933
+ prompt,
2934
+ {},
2935
+ { maxTokens: request.wordCount * 2, temperature: 0.7 }
2936
+ );
2937
+ return {
2938
+ id: generateId("blog"),
2939
+ ...parsed,
2940
+ blogType: request.blogType,
2941
+ tone: request.tone,
2942
+ wordCount: request.wordCount,
2943
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2944
+ };
2945
+ }
2946
+ };
2947
+ async function batchRequest(requests, options = {}) {
2948
+ const {
2949
+ concurrency = 3,
2950
+ stopOnError = false
2951
+ } = options;
2952
+ const results = [];
2953
+ const errors = [];
2954
+ for (let i = 0; i < requests.length; i += concurrency) {
2955
+ const batch = requests.slice(i, i + concurrency);
2956
+ const batchResults = await Promise.allSettled(
2957
+ batch.map((fn) => fn())
2958
+ );
2959
+ for (const result of batchResults) {
2960
+ if (result.status === "fulfilled") {
2961
+ results.push(result.value);
2962
+ } else {
2963
+ errors.push(result.reason);
2964
+ if (stopOnError) {
2965
+ throw new Error(
2966
+ `Batch request failed: ${errors[0].message}`
2967
+ );
2968
+ }
2969
+ }
2970
+ }
2971
+ }
2972
+ if (errors.length > 0 && !stopOnError) {
2973
+ console.warn(`${errors.length} requests failed in batch`);
2974
+ }
2975
+ return results;
2976
+ }
2977
+
2978
+ // src/content/domain/validation/HashtagCountValidator.ts
2979
+ function validateHashtagCount(count) {
2980
+ if (!Number.isFinite(count)) {
2981
+ throw new ValidationError(
2982
+ `Hashtag count must be between ${ValidationLimits.HASHTAG_COUNT.MIN} and ${ValidationLimits.HASHTAG_COUNT.MAX}`
2983
+ );
2984
+ }
2985
+ if (count < ValidationLimits.HASHTAG_COUNT.MIN || count > ValidationLimits.HASHTAG_COUNT.MAX) {
2986
+ throw new ValidationError(
2987
+ `Hashtag count must be between ${ValidationLimits.HASHTAG_COUNT.MIN} and ${ValidationLimits.HASHTAG_COUNT.MAX}`
2988
+ );
2989
+ }
2990
+ return Math.floor(count);
2991
+ }
2992
+
2993
+ // src/content/application/services/SocialService.ts
2994
+ var MULTI_PLATFORMS = [
2995
+ "twitter",
2996
+ "linkedin",
2997
+ "instagram",
2998
+ "threads",
2999
+ "tiktok"
3000
+ ];
3001
+ var DEFAULT_SOCIAL_RESULT = {
3002
+ content: "",
3003
+ platform: "twitter",
3004
+ hashtags: [],
3005
+ emojis: [],
3006
+ characterCount: 0,
3007
+ estimatedEngagement: 0
3008
+ };
3009
+ var SocialService = class {
3010
+ constructor(executor) {
3011
+ this.executor = executor;
3012
+ }
3013
+ executor;
3014
+ async generateSocialContent(request) {
3015
+ const topic = validateTopicText(request.topic);
3016
+ const spec = resolvePlatformSpec(request.platform);
3017
+ const maxLength = resolveMaxContentLength(request.platform, request.maxLength);
3018
+ const prompt = PromptBuilder.socialPost({
3019
+ topic,
3020
+ platform: request.platform,
3021
+ tone: request.tone,
3022
+ style: spec.style,
3023
+ maxLength,
3024
+ includeHashtags: request.hashtags !== false,
3025
+ includeCallToAction: Boolean(request.includeCallToAction)
3026
+ });
3027
+ const parsed = await this.executor.runJson(
3028
+ prompt,
3029
+ { ...DEFAULT_SOCIAL_RESULT, platform: request.platform },
3030
+ { maxTokens: 500, temperature: 0.8 }
3031
+ );
3032
+ return {
3033
+ ...parsed,
3034
+ platform: request.platform,
3035
+ characterCount: parsed.content?.length ?? 0
3036
+ };
3037
+ }
3038
+ async generateForAllPlatforms(topic, tone) {
3039
+ const requests = MULTI_PLATFORMS.map(
3040
+ (platform) => () => this.generateSocialContent({ topic, platform, tone })
3041
+ );
3042
+ try {
3043
+ return await batchRequest(requests, { concurrency: 2, stopOnError: false });
3044
+ } catch (error) {
3045
+ console.warn("Batch execution failed, falling back to sequential:", error);
3046
+ const results = [];
3047
+ for (const platform of MULTI_PLATFORMS) {
3048
+ try {
3049
+ results.push(await this.generateSocialContent({ topic, platform, tone }));
3050
+ } catch (err) {
3051
+ console.error(`Failed to generate for ${platform}:`, err);
3052
+ }
3053
+ }
3054
+ return results;
3055
+ }
3056
+ }
3057
+ async generateHashtags(content, count) {
3058
+ const validated = validateHashtagCount(count);
3059
+ const prompt = PromptBuilder.hashtags(content, validated);
3060
+ const response = await this.executor.runText(prompt, { maxTokens: 200, temperature: 0.7 });
3061
+ const hashtags = response.match(/#[\w-]+/g) || [];
3062
+ return hashtags.slice(0, validated);
3063
+ }
3064
+ optimizeHashtags(hashtags, platform) {
3065
+ const budget = resolveHashtagBudget(platform);
3066
+ return rankAndTrimHashtags(hashtags, budget);
3067
+ }
3068
+ };
3069
+
3070
+ // src/content/domain/validation/DurationSecondsValidator.ts
3071
+ function validateDurationSeconds(duration) {
3072
+ if (!Number.isFinite(duration)) {
3073
+ throw new ValidationError(
3074
+ `Duration must be between ${ValidationLimits.DURATION_SECONDS.MIN} and ${ValidationLimits.DURATION_SECONDS.MAX} seconds`
3075
+ );
3076
+ }
3077
+ if (duration < ValidationLimits.DURATION_SECONDS.MIN || duration > ValidationLimits.DURATION_SECONDS.MAX) {
3078
+ throw new ValidationError(
3079
+ `Duration must be between ${ValidationLimits.DURATION_SECONDS.MIN} and ${ValidationLimits.DURATION_SECONDS.MAX} seconds`
3080
+ );
3081
+ }
3082
+ return duration;
3083
+ }
3084
+
3085
+ // src/content/application/services/VideoScriptService.ts
3086
+ var buildScriptResult = (parsed, duration) => {
3087
+ const script = parsed.script ?? "";
3088
+ if (!script) {
3089
+ throw new Error("AI response missing script field");
3090
+ }
3091
+ return {
3092
+ ...parsed,
3093
+ script,
3094
+ duration,
3095
+ wordCount: script.split(" ").length
3096
+ };
3097
+ };
3098
+ var VideoScriptService = class {
3099
+ constructor(executor) {
3100
+ this.executor = executor;
3101
+ }
3102
+ executor;
3103
+ async generateVideoScript(request) {
3104
+ const topic = validateTopicText(request.topic);
3105
+ const duration = validateDurationSeconds(request.duration);
3106
+ const prompt = PromptBuilder.videoScript({
3107
+ topic,
3108
+ tone: request.tone,
3109
+ duration,
3110
+ targetAudience: request.targetAudience,
3111
+ includeVisuals: request.includeVisuals,
3112
+ includeCallToAction: request.includeCallToAction
3113
+ });
3114
+ const parsed = await this.executor.runJson(
3115
+ prompt,
3116
+ { script: "", visualCues: [], duration, wordCount: 0, estimatedEngagement: 0 },
3117
+ { maxTokens: estimateTokenBudgetForDuration(duration), temperature: 0.8 }
3118
+ );
3119
+ return buildScriptResult(parsed, duration);
3120
+ }
3121
+ async generateVoiceScript(topic, emotion, duration) {
3122
+ const validatedTopic = validateTopicText(topic);
3123
+ const validatedDuration = validateDurationSeconds(duration);
3124
+ const prompt = PromptBuilder.voiceScript({
3125
+ topic: validatedTopic,
3126
+ emotion,
3127
+ duration: validatedDuration
3128
+ });
3129
+ const parsed = await this.executor.runJson(
3130
+ prompt,
3131
+ { script: "", visualCues: [], duration: validatedDuration, wordCount: 0, estimatedEngagement: 0 },
3132
+ { maxTokens: estimateTokenBudgetForDuration(validatedDuration), temperature: 0.8 }
3133
+ );
3134
+ return buildScriptResult(parsed, validatedDuration);
3135
+ }
3136
+ };
3137
+
3138
+ // src/content/domain/validation/ContentBodyValidator.ts
3139
+ function validateContentBody(content) {
3140
+ if (typeof content !== "string" || content.trim().length < ValidationLimits.CONTENT.MIN) {
3141
+ throw new ValidationError("Content cannot be empty");
3142
+ }
3143
+ const trimmed = content.trim();
3144
+ if (trimmed.length > ValidationLimits.CONTENT.MAX) {
3145
+ throw new ValidationError(`Content exceeds maximum length of ${ValidationLimits.CONTENT.MAX} characters`);
3146
+ }
3147
+ return trimmed;
3148
+ }
3149
+ function truncateForSeoPreview(content) {
3150
+ return content.substring(0, ValidationLimits.SEO_CONTENT_PREVIEW);
3151
+ }
3152
+ function truncateForBlogPromptPreview(content) {
3153
+ return content.substring(0, ValidationLimits.BLOG_PROMPT_PREVIEW);
3154
+ }
3155
+ function truncateForAbVariantPreview(content) {
3156
+ return content.substring(0, ValidationLimits.AB_VARIANT_CONTENT_PREVIEW);
3157
+ }
3158
+
3159
+ // src/content/application/services/AnalysisService.ts
3160
+ var DEFAULT_SENTIMENT = {
3161
+ sentiment: "neutral",
3162
+ confidence: 0.5,
3163
+ emotions: []
3164
+ };
3165
+ var DEFAULT_ANALYSIS = {
3166
+ sentiment: DEFAULT_SENTIMENT,
3167
+ keywords: [],
3168
+ entities: [],
3169
+ suggestedImprovements: [],
3170
+ readabilityScore: 50,
3171
+ estimatedEngagement: 50
3172
+ };
3173
+ var AnalysisService = class {
3174
+ constructor(executor) {
3175
+ this.executor = executor;
3176
+ }
3177
+ executor;
3178
+ async analyzeSentiment(content) {
3179
+ const validated = validateContentBody(content);
3180
+ const prompt = PromptBuilder.sentiment(validated);
3181
+ const result = await this.executor.runJson(
3182
+ prompt,
3183
+ DEFAULT_SENTIMENT,
3184
+ { maxTokens: 200, temperature: 0.3 }
3185
+ );
3186
+ return {
3187
+ ...result,
3188
+ confidence: clampConfidenceToValidRange(result.confidence)
3189
+ };
3190
+ }
3191
+ async analyzeContent(request) {
3192
+ const validated = validateContentBody(request.content);
3193
+ const sentimentResult = await this.analyzeSentiment(validated);
3194
+ const prompt = PromptBuilder.contentAnalysis(validated);
3195
+ const parsed = await this.executor.runJson(
3196
+ prompt,
3197
+ { ...DEFAULT_ANALYSIS, sentiment: sentimentResult },
3198
+ { maxTokens: 300, temperature: 0.5 }
3199
+ );
3200
+ return {
3201
+ sentiment: sentimentResult,
3202
+ keywords: parsed.keywords ?? [],
3203
+ entities: parsed.entities ?? [],
3204
+ suggestedImprovements: parsed.suggestedImprovements ?? [],
3205
+ readabilityScore: clampScoreToValidRange(parsed.readabilityScore),
3206
+ estimatedEngagement: clampScoreToValidRange(parsed.estimatedEngagement)
3207
+ };
3208
+ }
3209
+ };
3210
+
3211
+ // src/content/domain/calculations/SeoKeywordDensity.ts
3212
+ function countKeywordOccurrences(content, keyword) {
3213
+ if (!keyword) return 0;
3214
+ const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3215
+ const matches = content.match(new RegExp(escaped, "gi"));
3216
+ return matches ? matches.length : 0;
3217
+ }
3218
+ function countWordsInContent(content) {
3219
+ const tokens = content.split(/\s+/).filter(Boolean);
3220
+ return tokens.length || 1;
3221
+ }
3222
+ function calculateKeywordDensity(content, keyword) {
3223
+ const totalWords = countWordsInContent(content);
3224
+ const occurrences = countKeywordOccurrences(content, keyword);
3225
+ return occurrences / totalWords * 100;
3226
+ }
3227
+
3228
+ // src/content/application/services/SeoService.ts
3229
+ var DEFAULT_SEO_SCORE = {
3230
+ keywordDensity: 50,
3231
+ readabilityScore: 50,
3232
+ titleOptimization: 50,
3233
+ metaDescription: 50,
3234
+ headingStructure: 50,
3235
+ internalLinking: 50,
3236
+ overall: 50
3237
+ };
3238
+ var UNDERUTILIZED_SUGGESTION = "Consider using this keyword more frequently";
3239
+ var SeoService = class {
3240
+ constructor(executor) {
3241
+ this.executor = executor;
3242
+ }
3243
+ executor;
3244
+ async optimizeSEO(request) {
3245
+ const content = validateContentBody(request.content);
3246
+ const keywords = validateKeywordList(request.keywords);
3247
+ const prompt = PromptBuilder.seoOptimization(content, keywords);
3248
+ const result = await this.executor.runJson(
3249
+ prompt,
3250
+ {
3251
+ optimized: content,
3252
+ score: 50,
3253
+ suggestions: [],
3254
+ addedKeywords: [],
3255
+ removedFillerWords: 0,
3256
+ readabilityImprovements: []
3257
+ },
3258
+ { maxTokens: 1e3, temperature: 0.7 }
3259
+ );
3260
+ return {
3261
+ ...result,
3262
+ score: clampScoreToValidRange(result.score)
3263
+ };
3264
+ }
3265
+ async calculateSEOScore(content, keywords) {
3266
+ const validatedContent = validateContentBody(content);
3267
+ const validatedKeywords = validateKeywordList(keywords);
3268
+ const prompt = PromptBuilder.seoOptimization(
3269
+ truncateForSeoPreview(validatedContent),
3270
+ validatedKeywords
3271
+ );
3272
+ const result = await this.executor.runJson(
3273
+ prompt,
3274
+ DEFAULT_SEO_SCORE,
3275
+ { maxTokens: 200, temperature: 0.3 }
3276
+ );
3277
+ return {
3278
+ keywordDensity: clampScoreToValidRange(result.keywordDensity),
3279
+ readabilityScore: clampScoreToValidRange(result.readabilityScore),
3280
+ titleOptimization: clampScoreToValidRange(result.titleOptimization),
3281
+ metaDescription: clampScoreToValidRange(result.metaDescription),
3282
+ headingStructure: clampScoreToValidRange(result.headingStructure),
3283
+ internalLinking: clampScoreToValidRange(result.internalLinking),
3284
+ overall: clampScoreToValidRange(result.overall)
3285
+ };
3286
+ }
3287
+ analyzeKeywords(content, keywords) {
3288
+ const validatedContent = validateContentBody(content);
3289
+ const validatedKeywords = validateKeywordList(keywords);
3290
+ void countWordsInContent(validatedContent);
3291
+ return validatedKeywords.map((keyword) => {
3292
+ const count = countKeywordOccurrences(validatedContent, keyword);
3293
+ const density = calculateKeywordDensity(validatedContent, keyword);
3294
+ return {
3295
+ keyword,
3296
+ count,
3297
+ density,
3298
+ prominence: classifyKeywordProminence(density),
3299
+ suggestions: isUnderutilizedKeyword(density) ? [UNDERUTILIZED_SUGGESTION] : []
3300
+ };
3301
+ });
3302
+ }
3303
+ };
3304
+
3305
+ // src/content/domain/calculations/AbTestImprovementRatio.ts
3306
+ function calculateImprovementPercentage(scoreA, scoreB) {
3307
+ const higher = Math.max(scoreA, scoreB);
3308
+ const lower = Math.min(scoreA, scoreB);
3309
+ if (lower <= 0) {
3310
+ return "100";
3311
+ }
3312
+ const delta = (higher / lower - 1) * 100;
3313
+ return Math.abs(delta).toFixed(0);
3314
+ }
3315
+ function selectHigherScoreVariantId(scoreA, scoreB, idA = "A", idB = "B") {
3316
+ return scoreA > scoreB ? idA : idB;
3317
+ }
3318
+
3319
+ // src/content/application/services/ABTestService.ts
3320
+ var DEFAULT_PREDICTION = {
3321
+ variantId: "",
3322
+ predictedEngagement: 0,
3323
+ predictedCTR: 0,
3324
+ predictedConversions: 0,
3325
+ confidence: 0,
3326
+ reasoning: "",
3327
+ strengths: [],
3328
+ weaknesses: [],
3329
+ suggestions: []
3330
+ };
3331
+ var VARIANT_A_ID = "A";
3332
+ var VARIANT_B_ID = "B";
3333
+ var FALLBACK_ENGAGEMENT = 50;
3334
+ var DEFAULT_RECOMMENDATIONS = [
3335
+ "Test with larger audience",
3336
+ "Monitor click-through rates"
3337
+ ];
3338
+ var ABTestService = class {
3339
+ constructor(executor) {
3340
+ this.executor = executor;
3341
+ }
3342
+ executor;
3343
+ async predictABTest(request) {
3344
+ const predictions = [];
3345
+ for (const variant of request.variants) {
3346
+ const prompt = PromptBuilder.abTestVariant({
3347
+ content: truncateForAbVariantPreview(variant.content),
3348
+ contentType: variant.contentType,
3349
+ tone: variant.tone,
3350
+ targetAudience: request.targetAudience,
3351
+ platform: request.platform,
3352
+ goals: request.goals
3353
+ });
3354
+ const parsed = await this.executor.runJson(
3355
+ prompt,
3356
+ { ...DEFAULT_PREDICTION, variantId: variant.id },
3357
+ { maxTokens: 300, temperature: 0.5 }
3358
+ );
3359
+ predictions.push({
3360
+ variantId: variant.id,
3361
+ predictedEngagement: clampScoreToValidRange(parsed.predictedEngagement),
3362
+ predictedCTR: clampScoreToValidRange(parsed.predictedCTR),
3363
+ predictedConversions: clampScoreToValidRange(parsed.predictedConversions),
3364
+ confidence: clampConfidenceToValidRange(parsed.confidence),
3365
+ reasoning: parsed.reasoning ?? "",
3366
+ strengths: parsed.strengths ?? [],
3367
+ weaknesses: parsed.weaknesses ?? [],
3368
+ suggestions: parsed.suggestions ?? []
3369
+ });
3370
+ }
3371
+ return predictions;
3372
+ }
3373
+ async compareVariants(variantA, variantB) {
3374
+ const predictions = await this.predictABTest({
3375
+ variants: [
3376
+ { id: VARIANT_A_ID, content: variantA, contentType: "social", tone: "casual" },
3377
+ { id: VARIANT_B_ID, content: variantB, contentType: "social", tone: "casual" }
3378
+ ],
3379
+ targetAudience: "general",
3380
+ platform: "twitter",
3381
+ goals: ["engagement", "clicks"]
3382
+ });
3383
+ const scoreA = predictions[0]?.predictedEngagement ?? FALLBACK_ENGAGEMENT;
3384
+ const scoreB = predictions[1]?.predictedEngagement ?? FALLBACK_ENGAGEMENT;
3385
+ const winner = selectHigherScoreVariantId(scoreA, scoreB, VARIANT_A_ID, VARIANT_B_ID);
3386
+ const improvement = calculateImprovementPercentage(scoreA, scoreB);
3387
+ return {
3388
+ winner,
3389
+ confidence: AB_TEST_DEFAULT_CONFIDENCE,
3390
+ improvement: `+${improvement}% engagement`,
3391
+ reasoning: winner === VARIANT_A_ID ? "Variant A has more engaging content" : "Variant B has more engaging content",
3392
+ recommendations: [...DEFAULT_RECOMMENDATIONS]
3393
+ };
3394
+ }
3395
+ };
3396
+
3397
+ // src/content/infrastructure/utils/jsonParser.ts
3398
+ function repairCommonJsonDefects(text) {
3399
+ return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
3400
+ }
3401
+ function safeJSONParse(text, fallback, onError) {
3402
+ try {
3403
+ return JSON.parse(text);
3404
+ } catch (error) {
3405
+ try {
3406
+ return JSON.parse(repairCommonJsonDefects(text));
3407
+ } catch {
3408
+ }
3409
+ if (error instanceof SyntaxError) {
3410
+ const positionMatch = error.message.match(/position (\d+)/);
3411
+ const position = positionMatch ? parseInt(positionMatch[1], 10) : -1;
3412
+ if (position > 0) {
3413
+ text.substring(Math.max(0, position - 50), position + 50);
3414
+ }
3415
+ }
3416
+ return fallback;
3417
+ }
3418
+ }
3419
+ function extractJSON(text) {
3420
+ const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
3421
+ if (codeBlockMatch && codeBlockMatch[1]) {
3422
+ const candidate = codeBlockMatch[1].trim();
3423
+ if (candidate.startsWith("{") || candidate.startsWith("[")) {
3424
+ return candidate;
3425
+ }
3426
+ }
3427
+ const objectMatch = text.match(/\{[\s\S]*\}/);
3428
+ if (objectMatch) {
3429
+ return objectMatch[0];
3430
+ }
3431
+ const arrayMatch = text.match(/\[[\s\S]*\]/);
3432
+ if (arrayMatch) {
3433
+ return arrayMatch[0];
3434
+ }
3435
+ return null;
3436
+ }
3437
+ function parseAIResponse(response, fallback, onError) {
3438
+ const json = extractJSON(response);
3439
+ if (!json) {
3440
+ return fallback;
3441
+ }
3442
+ return safeJSONParse(json, fallback);
3443
+ }
3444
+
3445
+ // src/content/application/services/ImagePromptService.ts
3446
+ var JSON_ARRAY_PATTERN = /\[[\s\S]*\]/;
3447
+ var ImagePromptService = class {
3448
+ constructor(executor) {
3449
+ this.executor = executor;
3450
+ }
3451
+ executor;
3452
+ async generateImagePrompt(description, style) {
3453
+ const validated = validateTopicText(description);
3454
+ const prompt = PromptBuilder.imagePrompt(validated, style);
3455
+ return this.executor.runText(prompt, { maxTokens: 300, temperature: 0.8 });
3456
+ }
3457
+ async generateImagePromptsForBlog(blogContent) {
3458
+ const truncated = truncateForBlogPromptPreview(validateContentBody(blogContent));
3459
+ const prompt = PromptBuilder.blogImagePrompts(truncated);
3460
+ const response = await this.executor.runText(prompt, { maxTokens: 500, temperature: 0.8 });
3461
+ const jsonMatch = response.match(JSON_ARRAY_PATTERN);
3462
+ if (!jsonMatch) return [];
3463
+ return safeJSONParse(jsonMatch[0], []);
3464
+ }
3465
+ };
3466
+
3467
+ // src/content/domain/validation/CalendarDaysValidator.ts
3468
+ function validateCalendarDays(days) {
3469
+ if (!Number.isFinite(days)) {
3470
+ throw new ValidationError(
3471
+ `Days must be between ${ValidationLimits.CALENDAR_DAYS.MIN} and ${ValidationLimits.CALENDAR_DAYS.MAX}`
3472
+ );
3473
+ }
3474
+ if (days < ValidationLimits.CALENDAR_DAYS.MIN || days > ValidationLimits.CALENDAR_DAYS.MAX) {
3475
+ throw new ValidationError(
3476
+ `Days must be between ${ValidationLimits.CALENDAR_DAYS.MIN} and ${ValidationLimits.CALENDAR_DAYS.MAX}`
3477
+ );
3478
+ }
3479
+ return days;
3480
+ }
3481
+
3482
+ // src/content/domain/calculations/ContentCalendarDateOffset.ts
3483
+ var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
3484
+ function resolveCalendarEntryDate(dayIndex, baseTimestamp = Date.now()) {
3485
+ return new Date(baseTimestamp + (dayIndex - 1) * ONE_DAY_MS);
3486
+ }
3487
+
3488
+ // src/content/application/services/ContentCalendarService.ts
3489
+ var toCalendarEntry = (item) => ({
3490
+ date: resolveCalendarEntryDate(item.day),
3491
+ topic: item.topic,
3492
+ contentType: item.contentType,
3493
+ platform: item.platform,
3494
+ description: item.description,
3495
+ estimatedEngagement: item.estimatedEngagement,
3496
+ priority: item.priority
3497
+ });
3498
+ var ContentCalendarService = class {
3499
+ constructor(executor) {
3500
+ this.executor = executor;
3501
+ }
3502
+ executor;
3503
+ async generateContentCalendar(niche, days) {
3504
+ const validatedNiche = validateTopicText(niche);
3505
+ const validatedDays = validateCalendarDays(days);
3506
+ const prompt = PromptBuilder.contentCalendar(validatedNiche, validatedDays);
3507
+ const data = await this.executor.runJson(prompt, [], {
3508
+ maxTokens: estimateTokenBudgetForCalendarDays(validatedDays),
3509
+ temperature: 0.8
3510
+ });
3511
+ return data.map(toCalendarEntry);
3512
+ }
3513
+ };
3514
+
3515
+ // src/content/application/services/ProviderServices.ts
3516
+ var wrapProviderError = (operation, error) => {
3517
+ const message = error instanceof Error ? error.message : "Unknown error";
3518
+ return new AIError(`${operation} failed: ${message}`, "PROVIDER_ERROR", error);
3519
+ };
3520
+ var ensureFactory = (factory, operation) => {
3521
+ if (factory.getAllProviders().length === 0) {
3522
+ throw new AIError(
3523
+ `${operation} failed: No providers are registered. Provide a ProviderConfig with at least one enabled provider when constructing AIContentService.`,
3524
+ "PROVIDER_UNAVAILABLE"
3525
+ );
3526
+ }
3527
+ };
3528
+ var MediaGenerationService = class {
3529
+ constructor(providerFactory) {
3530
+ this.providerFactory = providerFactory;
3531
+ }
3532
+ providerFactory;
3533
+ generateImage(request) {
3534
+ ensureFactory(this.providerFactory, "Image generation");
3535
+ return this.providerFactory.generateImage(request).catch((err) => {
3536
+ throw wrapProviderError("Image generation", err);
3537
+ });
3538
+ }
3539
+ generateVideo(request) {
3540
+ ensureFactory(this.providerFactory, "Video generation");
3541
+ return this.providerFactory.generateVideo(request).catch((err) => {
3542
+ throw wrapProviderError("Video generation", err);
3543
+ });
3544
+ }
3545
+ convertImageToVideo(request) {
3546
+ ensureFactory(this.providerFactory, "Image-to-video conversion");
3547
+ return this.providerFactory.imageToVideo(request).catch((err) => {
3548
+ throw wrapProviderError("Image-to-video conversion", err);
3549
+ });
3550
+ }
3551
+ transformVideo(request) {
3552
+ ensureFactory(this.providerFactory, "Video-to-video transformation");
3553
+ return this.providerFactory.videoToVideo(request).catch((err) => {
3554
+ throw wrapProviderError("Video-to-video transformation", err);
3555
+ });
3556
+ }
3557
+ };
3558
+
3559
+ // src/content/application/services/GenerationExecutor.ts
3560
+ var GenerationExecutor = class {
3561
+ constructor(generator) {
3562
+ this.generator = generator;
3563
+ }
3564
+ generator;
3565
+ async runJson(prompt, fallback, generationOptions = {}) {
3566
+ const response = await this.generator.generateText(prompt, generationOptions);
3567
+ return parseAIResponse(response, fallback);
3568
+ }
3569
+ runText(prompt, options = {}) {
3570
+ return this.generator.generateText(prompt, options);
3571
+ }
3572
+ /** Usage-complete pass-through; requires a ProviderFactory engine. */
3573
+ runChat(request) {
3574
+ if (!(this.generator instanceof ProviderFactory)) {
3575
+ throw new AIError(
3576
+ "runChat requires a ProviderFactory engine \u2014 construct AIContentService with a provider config, not a custom ITextGenerator",
3577
+ "PROVIDER_UNAVAILABLE"
3578
+ );
3579
+ }
3580
+ return this.generator.chat(request);
3581
+ }
3582
+ };
3583
+
3584
+ // src/content/application/services/AIContentService.ts
3585
+ var isTextGenerator = (value) => typeof value === "object" && value !== null && typeof value.generateText === "function";
3586
+ function toAIProviderConfig(config) {
3587
+ const legacy = config;
3588
+ const modern = config;
3589
+ const groq = modern.groq ?? (legacy.groq?.enabled ? {
3590
+ apiKey: legacy.groq.apiKey,
3591
+ baseUrl: legacy.groq.baseUrl,
3592
+ timeoutMs: legacy.groq.timeout,
3593
+ // legacy {text, chat} model pins map onto the neutral tier slots
3594
+ ...legacy.groq.models?.text ? { models: { balanced: legacy.groq.models.text } } : {}
3595
+ } : void 0);
3596
+ const gemini = modern.gemini ?? (legacy.gemini?.enabled ? { ...legacy.gemini } : void 0);
3597
+ const fal = modern.fal ?? (legacy.fal?.enabled ? { ...legacy.fal } : void 0);
3598
+ const pruna = modern.pruna ?? (legacy.pruna?.enabled ? { ...legacy.pruna } : void 0);
3599
+ return {
3600
+ priority: modern.priority,
3601
+ fallbackEnabled: modern.fallbackEnabled,
3602
+ defaultTier: modern.defaultTier,
3603
+ anthropic: modern.anthropic,
3604
+ ...groq ? { groq } : {},
3605
+ ...gemini ? { gemini } : {},
3606
+ ...fal ? { fal } : {},
3607
+ ...pruna ? { pruna } : {}
3608
+ };
3609
+ }
3610
+ var buildServices = (configOrGenerator) => {
3611
+ if (isTextGenerator(configOrGenerator)) {
3612
+ const executor2 = new GenerationExecutor(configOrGenerator);
3613
+ const emptyFactory = new ProviderFactory({
3614
+ priority: [],
3615
+ fallbackEnabled: false
3616
+ });
3617
+ return {
3618
+ providerFactory: emptyFactory,
3619
+ blogService: new BlogService(executor2),
3620
+ socialService: new SocialService(executor2),
3621
+ videoScriptService: new VideoScriptService(executor2),
3622
+ analysisService: new AnalysisService(executor2),
3623
+ seoService: new SeoService(executor2),
3624
+ abTestService: new ABTestService(executor2),
3625
+ imagePromptService: new ImagePromptService(executor2),
3626
+ contentCalendarService: new ContentCalendarService(executor2),
3627
+ mediaGenerationService: new MediaGenerationService(emptyFactory)
3628
+ };
3629
+ }
3630
+ const factory = createAI(toAIProviderConfig(configOrGenerator));
3631
+ const executor = new GenerationExecutor(factory);
3632
+ return {
3633
+ providerFactory: factory,
3634
+ blogService: new BlogService(executor),
3635
+ socialService: new SocialService(executor),
3636
+ videoScriptService: new VideoScriptService(executor),
3637
+ analysisService: new AnalysisService(executor),
3638
+ seoService: new SeoService(executor),
3639
+ abTestService: new ABTestService(executor),
3640
+ imagePromptService: new ImagePromptService(executor),
3641
+ contentCalendarService: new ContentCalendarService(executor),
3642
+ mediaGenerationService: new MediaGenerationService(factory)
3643
+ };
3644
+ };
3645
+ var AIContentService = class {
3646
+ providerFactory;
3647
+ blogService;
3648
+ socialService;
3649
+ videoScriptService;
3650
+ analysisService;
3651
+ seoService;
3652
+ abTestService;
3653
+ imagePromptService;
3654
+ contentCalendarService;
3655
+ mediaGenerationService;
3656
+ constructor(configOrGenerator) {
3657
+ const bundle = buildServices(configOrGenerator);
3658
+ this.providerFactory = bundle.providerFactory;
3659
+ this.blogService = bundle.blogService;
3660
+ this.socialService = bundle.socialService;
3661
+ this.videoScriptService = bundle.videoScriptService;
3662
+ this.analysisService = bundle.analysisService;
3663
+ this.seoService = bundle.seoService;
3664
+ this.abTestService = bundle.abTestService;
3665
+ this.imagePromptService = bundle.imagePromptService;
3666
+ this.contentCalendarService = bundle.contentCalendarService;
3667
+ this.mediaGenerationService = bundle.mediaGenerationService;
3668
+ }
3669
+ /** The composed factory — chat()/chatStream() with usage, health, fallback. */
3670
+ get providers() {
3671
+ return this.providerFactory;
3672
+ }
3673
+ generateBlogPost(request) {
3674
+ return this.blogService.generateBlogPost(request);
3675
+ }
3676
+ generateSocialContent(request) {
3677
+ return this.socialService.generateSocialContent(request);
3678
+ }
3679
+ generateForAllPlatforms(topic, tone) {
3680
+ return this.socialService.generateForAllPlatforms(topic, tone);
3681
+ }
3682
+ generateHashtags(content, count) {
3683
+ return this.socialService.generateHashtags(content, count);
3684
+ }
3685
+ optimizeHashtags(hashtags, platform) {
3686
+ return Promise.resolve(this.socialService.optimizeHashtags(hashtags, platform));
3687
+ }
3688
+ generateVideoScript(request) {
3689
+ return this.videoScriptService.generateVideoScript(request);
3690
+ }
3691
+ generateVoiceScript(topic, emotion, duration) {
3692
+ return this.videoScriptService.generateVoiceScript(topic, emotion, duration);
3693
+ }
3694
+ generateContentCalendar(niche, days) {
3695
+ return this.contentCalendarService.generateContentCalendar(niche, days);
3696
+ }
3697
+ analyzeSentiment(content) {
3698
+ return this.analysisService.analyzeSentiment(content);
3699
+ }
3700
+ analyzeContent(request) {
3701
+ return this.analysisService.analyzeContent(request);
3702
+ }
3703
+ optimizeSEO(request) {
3704
+ return this.seoService.optimizeSEO(request);
3705
+ }
3706
+ calculateSEOScore(content, keywords) {
3707
+ return this.seoService.calculateSEOScore(content, keywords);
3708
+ }
3709
+ analyzeKeywords(content, keywords) {
3710
+ return Promise.resolve(this.seoService.analyzeKeywords(content, keywords));
3711
+ }
3712
+ predictABTest(request) {
3713
+ return this.abTestService.predictABTest(request);
3714
+ }
3715
+ compareVariants(variantA, variantB) {
3716
+ return this.abTestService.compareVariants(variantA, variantB);
3717
+ }
3718
+ generateImagePrompt(description, style) {
3719
+ return this.imagePromptService.generateImagePrompt(description, style);
3720
+ }
3721
+ generateImagePromptsForBlog(blogContent) {
3722
+ return this.imagePromptService.generateImagePromptsForBlog(blogContent);
3723
+ }
3724
+ generateImage(request) {
3725
+ return this.mediaGenerationService.generateImage(request);
3726
+ }
3727
+ generateVideo(request) {
3728
+ return this.mediaGenerationService.generateVideo(request);
3729
+ }
3730
+ convertImageToVideo(request) {
3731
+ return this.mediaGenerationService.convertImageToVideo(request);
3732
+ }
3733
+ transformVideo(request) {
3734
+ return this.mediaGenerationService.transformVideo(request);
3735
+ }
3736
+ };
3737
+
3738
+ exports.ABTestService = ABTestService;
3739
+ exports.AIContentService = AIContentService;
3740
+ exports.AIError = AIError;
3741
+ exports.ASPECT_RATIO_OPTIONS = ASPECT_RATIO_OPTIONS;
3742
+ exports.ASPECT_RATIO_VALUES = ASPECT_RATIO_VALUES;
3743
+ exports.AnalysisService = AnalysisService;
3744
+ exports.BlogService = BlogService;
3745
+ exports.ContentCalendarService = ContentCalendarService;
3746
+ exports.GENERATION_COSTS = CREDIT_COSTS;
3747
+ exports.GenerationExecutor = GenerationExecutor;
3748
+ exports.IMAGE_QUALITY_OPTIONS = IMAGE_QUALITY_OPTIONS;
3749
+ exports.IMAGE_QUALITY_VALUES = IMAGE_QUALITY_VALUES;
3750
+ exports.ImagePromptService = ImagePromptService;
3751
+ exports.MOTION_OPTIONS = MOTION_OPTIONS;
3752
+ exports.MediaGenerationService = MediaGenerationService;
3753
+ exports.PromptBuilder = PromptBuilder;
3754
+ exports.ProviderQuotaExhaustedError = ProviderQuotaExhaustedError;
3755
+ exports.ProviderUnavailableError = ProviderUnavailableError;
3756
+ exports.STYLE_PRESETS = STYLE_PRESETS;
3757
+ exports.SeoService = SeoService;
3758
+ exports.SocialService = SocialService;
3759
+ exports.VIDEO_DURATION_OPTIONS = VIDEO_DURATION_OPTIONS;
3760
+ exports.VIDEO_DURATION_OPTIONS_SECONDS = VIDEO_DURATION_OPTIONS_SECONDS;
3761
+ exports.VIDEO_QUALITY_OPTIONS = VIDEO_QUALITY_OPTIONS;
3762
+ exports.VIDEO_QUALITY_VALUES = VIDEO_QUALITY_VALUES;
3763
+ exports.ValidationError = ValidationError;
3764
+ exports.VideoScriptService = VideoScriptService;
3765
+ exports.calculateCreditCost = calculateContentCreditCost;
3766
+ exports.getDimensionsForAspectRatio = getDimensionsForAspectRatio;
3767
+ exports.getRecommendedQuality = getRecommendedQuality;
3768
+ //# sourceMappingURL=index.cjs.map
3769
+ //# sourceMappingURL=index.cjs.map