@umituz/web-ai 1.0.1 → 1.0.3

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.
@@ -182,6 +182,14 @@ var ProviderError = class extends AIError {
182
182
  providerId;
183
183
  };
184
184
 
185
+ // src/core/errors/groq-error.ts
186
+ var GroqError = class extends AIError {
187
+ constructor(type, message, cause, metadata = {}) {
188
+ super(type, message, cause, metadata);
189
+ this.name = "GroqError";
190
+ }
191
+ };
192
+
185
193
  // src/core/errors/error-classifier.ts
186
194
  var DEFAULT_RETRYABLE_TYPES = /* @__PURE__ */ new Set([
187
195
  "NETWORK_ERROR" /* NETWORK_ERROR */,
@@ -530,6 +538,56 @@ function isSseDoneMarker(payload) {
530
538
  return payload === SSE_DONE_MARKER;
531
539
  }
532
540
 
541
+ // src/core/parsers/streaming-response.parser.ts
542
+ var StreamingResponseParser = class {
543
+ constructor(callbacks, extractDelta) {
544
+ this.callbacks = callbacks;
545
+ this.extractDelta = extractDelta;
546
+ }
547
+ callbacks;
548
+ extractDelta;
549
+ decoder = new TextDecoder();
550
+ async parse(stream) {
551
+ const reader = stream.getReader();
552
+ const contentChunks = [];
553
+ let buffer = "";
554
+ const consumeLine = (rawLine) => {
555
+ const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
556
+ const trimmed = line.trim();
557
+ if (trimmed.length === 0 || !isSseDataLine(trimmed)) return;
558
+ const payload = extractSseDataPayload(trimmed);
559
+ if (isSseDoneMarker(payload)) return;
560
+ const content = this.extractDelta(payload);
561
+ if (content !== null) {
562
+ contentChunks.push(content);
563
+ this.callbacks.onChunk(content);
564
+ }
565
+ };
566
+ try {
567
+ while (true) {
568
+ const { done, value } = await reader.read();
569
+ if (done) break;
570
+ buffer += this.decoder.decode(value, { stream: true });
571
+ const lines = buffer.split("\n");
572
+ buffer = lines.pop() ?? "";
573
+ for (const line of lines) consumeLine(line);
574
+ }
575
+ buffer += this.decoder.decode();
576
+ if (buffer.length > 0) consumeLine(buffer);
577
+ } catch (error) {
578
+ if (error instanceof AIError) throw error;
579
+ throw new AIError(
580
+ "NETWORK_ERROR" /* NETWORK_ERROR */,
581
+ "Stream was interrupted",
582
+ error instanceof Error ? error : void 0
583
+ );
584
+ } finally {
585
+ reader.releaseLock();
586
+ }
587
+ return contentChunks.join("");
588
+ }
589
+ };
590
+
533
591
  // src/core/parsers/json-content-extractor.ts
534
592
  function extractJsonFromCodeFence(content) {
535
593
  const match = content.match(JSON_CODE_BLOCK_PATTERN);
@@ -1188,6 +1246,12 @@ function calculateExponentialBackoff(params) {
1188
1246
  }
1189
1247
 
1190
1248
  // src/core/constants/identifier.constants.ts
1249
+ var MESSAGE_ID_PREFIX = "msg-";
1250
+ var CHAT_MESSAGE_ID_SUFFIX_LENGTH = 9;
1251
+ var CACHE_KEY_TYPE_COMPLETION = "completion";
1252
+ var CACHE_KEY_TYPE_CHAT = "chat";
1253
+ var USER_ROLE = "user";
1254
+ var ASSISTANT_ROLE = "assistant";
1191
1255
  var QUEUE_PRIORITY_USER_REQUEST = 10;
1192
1256
  var DEFAULT_RETRY_MAX_ATTEMPTS = 3;
1193
1257
  var DEFAULT_RETRY_INITIAL_DELAY_MS = 1e3;
@@ -1361,6 +1425,27 @@ var RequestDeduplicator = class {
1361
1425
  }
1362
1426
  };
1363
1427
 
1428
+ // src/core/utils/api-key-validator.ts
1429
+ var DEFAULT_API_KEY_RULE = {
1430
+ minLength: 10
1431
+ };
1432
+ function validateApiKey(apiKey, rule = DEFAULT_API_KEY_RULE) {
1433
+ if (!apiKey) {
1434
+ return { valid: false, reason: "API key is required" };
1435
+ }
1436
+ const trimmed = apiKey.trim();
1437
+ if (trimmed.length === 0) {
1438
+ return { valid: false, reason: "API key cannot be empty" };
1439
+ }
1440
+ if (trimmed.length < rule.minLength) {
1441
+ return {
1442
+ valid: false,
1443
+ reason: `API key must be at least ${rule.minLength} characters`
1444
+ };
1445
+ }
1446
+ return { valid: true };
1447
+ }
1448
+
1364
1449
  // src/providers/groq/groq.constants.ts
1365
1450
  var GROQ_MODELS = {
1366
1451
  LLAMA_3_1_8B_INSTANT: "llama-3.1-8b-instant",
@@ -1384,12 +1469,22 @@ var DEFAULT_GENERATION_CONFIG = {
1384
1469
  maxTokens: 1024,
1385
1470
  topP: 1
1386
1471
  };
1472
+ var DEFAULT_MODELS = {
1473
+ TEXT: GROQ_MODELS.LLAMA_3_1_8B_INSTANT,
1474
+ FAST: GROQ_MODELS.LLAMA_3_1_8B_INSTANT,
1475
+ EXPERIMENTAL: GROQ_MODELS.GPT_OSS_20B
1476
+ };
1387
1477
  var GROQ_BASE_URL = "https://api.groq.com/openai/v1";
1478
+ var DEFAULT_BASE_URL = GROQ_BASE_URL;
1388
1479
  var CHAT_COMPLETIONS_PATH = "/chat/completions";
1480
+ var API_ENDPOINTS = {
1481
+ CHAT: CHAT_COMPLETIONS_PATH
1482
+ };
1389
1483
  var GROQ_TIMEOUTS = {
1390
1484
  DEFAULT: 3e4,
1391
1485
  STREAMING: 6e4
1392
1486
  };
1487
+ var TIMEOUTS = GROQ_TIMEOUTS;
1393
1488
  var HTTP_HEADERS = {
1394
1489
  CONTENT_TYPE: "Content-Type",
1395
1490
  APPLICATION_JSON: "application/json",
@@ -1448,13 +1543,65 @@ var GroqChatRequestBuilder = class _GroqChatRequestBuilder {
1448
1543
  }
1449
1544
  };
1450
1545
 
1546
+ // src/providers/groq/groq-config-store.ts
1547
+ var GroqConfigStore = class {
1548
+ config = null;
1549
+ initialize(config) {
1550
+ const validation = validateApiKey(config.apiKey, { minLength: 1 });
1551
+ if (!validation.valid) {
1552
+ throw new GroqError("INVALID_API_KEY" /* INVALID_API_KEY */, validation.reason ?? "Invalid API key");
1553
+ }
1554
+ const normalized = {
1555
+ apiKey: config.apiKey.trim(),
1556
+ baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
1557
+ timeoutMs: config.timeoutMs ?? TIMEOUTS.DEFAULT,
1558
+ ...config.models !== void 0 ? { models: config.models } : {},
1559
+ ...config.textModel !== void 0 ? { textModel: config.textModel } : {}
1560
+ };
1561
+ this.config = normalized;
1562
+ return normalized;
1563
+ }
1564
+ get() {
1565
+ if (!this.config) {
1566
+ throw new GroqError(
1567
+ "MISSING_CONFIG" /* MISSING_CONFIG */,
1568
+ "Client not initialized. Call configureProvider first."
1569
+ );
1570
+ }
1571
+ return this.config;
1572
+ }
1573
+ getOrNull() {
1574
+ return this.config;
1575
+ }
1576
+ isInitialized() {
1577
+ return this.config !== null;
1578
+ }
1579
+ setApiKey(apiKey) {
1580
+ const current = this.get();
1581
+ const validation = validateApiKey(apiKey, { minLength: 1 });
1582
+ if (!validation.valid) {
1583
+ throw new GroqError("INVALID_API_KEY" /* INVALID_API_KEY */, validation.reason ?? "Invalid API key");
1584
+ }
1585
+ this.config = { ...current, apiKey: apiKey.trim() };
1586
+ }
1587
+ reset() {
1588
+ this.config = null;
1589
+ }
1590
+ };
1591
+
1451
1592
  // src/providers/groq/groq-http-transport.ts
1452
1593
  var GroqHttpTransport = class {
1453
- constructor(config) {
1454
- this.config = config;
1594
+ constructor(configSource) {
1595
+ this.configSource = configSource;
1455
1596
  }
1456
- config;
1597
+ configSource;
1457
1598
  pendingAbortControllers = /* @__PURE__ */ new Set();
1599
+ getConfig() {
1600
+ if ("get" in this.configSource && typeof this.configSource.get === "function") {
1601
+ return this.configSource.get();
1602
+ }
1603
+ return this.configSource;
1604
+ }
1458
1605
  cancelPendingRequests() {
1459
1606
  for (const controller of this.pendingAbortControllers) {
1460
1607
  controller.abort();
@@ -1469,7 +1616,7 @@ var GroqHttpTransport = class {
1469
1616
  }
1470
1617
  async streamChatCompletion(request, options = {}) {
1471
1618
  const url = `${this.baseUrl()}${CHAT_COMPLETIONS_PATH}`;
1472
- const timeout = this.config.timeoutMs ?? GROQ_TIMEOUTS.STREAMING;
1619
+ const timeout = this.getConfig().timeoutMs ?? GROQ_TIMEOUTS.STREAMING;
1473
1620
  const controller = new AbortController();
1474
1621
  const timeoutId = setTimeout(() => controller.abort(), timeout);
1475
1622
  const onExternalAbort = () => controller.abort();
@@ -1496,11 +1643,11 @@ var GroqHttpTransport = class {
1496
1643
  }
1497
1644
  }
1498
1645
  baseUrl() {
1499
- return this.config.baseUrl ?? GROQ_BASE_URL;
1646
+ return this.getConfig().baseUrl ?? GROQ_BASE_URL;
1500
1647
  }
1501
1648
  async request(endpoint, body) {
1502
1649
  const url = `${this.baseUrl()}${endpoint}`;
1503
- const timeout = this.config.timeoutMs ?? GROQ_TIMEOUTS.DEFAULT;
1650
+ const timeout = this.getConfig().timeoutMs ?? GROQ_TIMEOUTS.DEFAULT;
1504
1651
  const controller = new AbortController();
1505
1652
  const timeoutId = setTimeout(() => controller.abort(), timeout);
1506
1653
  this.pendingAbortControllers.add(controller);
@@ -1523,7 +1670,7 @@ var GroqHttpTransport = class {
1523
1670
  buildHeaders() {
1524
1671
  return {
1525
1672
  [HTTP_HEADERS.CONTENT_TYPE]: HTTP_HEADERS.APPLICATION_JSON,
1526
- [HTTP_HEADERS.AUTHORIZATION]: `${HTTP_HEADERS.BEARER_PREFIX}${this.config.apiKey}`
1673
+ [HTTP_HEADERS.AUTHORIZATION]: `${HTTP_HEADERS.BEARER_PREFIX}${this.getConfig().apiKey}`
1527
1674
  };
1528
1675
  }
1529
1676
  async throwFromErrorResponse(response) {
@@ -1625,21 +1772,40 @@ var CompositeExecutor = class {
1625
1772
 
1626
1773
  // src/providers/groq/groq-http-client.ts
1627
1774
  var GroqHttpClient = class {
1775
+ configStore;
1628
1776
  transport;
1629
1777
  executor;
1630
- constructor(config, deps = {}) {
1631
- this.transport = deps.transport ?? new GroqHttpTransport(config);
1778
+ constructor(configOrDeps, maybeDeps = {}) {
1779
+ const isConfig = typeof configOrDeps === "object" && configOrDeps !== null && "apiKey" in configOrDeps && typeof configOrDeps.apiKey === "string";
1780
+ const deps = isConfig ? maybeDeps : configOrDeps ?? {};
1781
+ this.configStore = deps.configStore ?? new GroqConfigStore();
1782
+ if (isConfig) {
1783
+ this.configStore.initialize(configOrDeps);
1784
+ }
1785
+ this.transport = deps.transport ?? new GroqHttpTransport(this.configStore);
1632
1786
  const retryManager2 = deps.retryManager ?? new RetryManager();
1633
1787
  const queue = deps.queue ?? new RequestQueue();
1634
1788
  const deduplicator = deps.deduplicator ?? new RequestDeduplicator();
1635
1789
  this.executor = new CompositeExecutor([
1636
1790
  new DedupeExecutor(deduplicator, {
1637
- generateKey: (request) => deduplicatorKey(request, config.apiKey)
1791
+ generateKey: (request) => deduplicatorKey(request, this.configStore.isInitialized() ? this.configStore.get().apiKey : "")
1638
1792
  }),
1639
1793
  new RetryExecutor(retryManager2),
1640
1794
  new QueueExecutor(queue, QUEUE_PRIORITY_USER_REQUEST)
1641
1795
  ]);
1642
1796
  }
1797
+ initialize(config) {
1798
+ this.configStore.initialize(config);
1799
+ }
1800
+ isInitialized() {
1801
+ return this.configStore.isInitialized();
1802
+ }
1803
+ getApiKey() {
1804
+ return this.configStore.get().apiKey;
1805
+ }
1806
+ setApiKey(apiKey) {
1807
+ this.configStore.setApiKey(apiKey);
1808
+ }
1643
1809
  cancelPendingRequests() {
1644
1810
  this.transport.cancelPendingRequests();
1645
1811
  }
@@ -1652,6 +1818,10 @@ var GroqHttpClient = class {
1652
1818
  streamChatCompletion(request, options) {
1653
1819
  return this.transport.streamChatCompletion(request, options);
1654
1820
  }
1821
+ reset() {
1822
+ this.transport.cancelPendingRequests();
1823
+ this.configStore.reset();
1824
+ }
1655
1825
  };
1656
1826
  function deduplicatorKey(request, apiKey) {
1657
1827
  const { model, messages, stream: _stream, ...rest } = request;
@@ -2383,39 +2553,811 @@ function createFalProvider(config) {
2383
2553
  return new FalProvider(config);
2384
2554
  }
2385
2555
 
2386
- // src/providers/pruna/pruna-generate.ts
2387
- var prunaCorePromise = null;
2388
- async function loadPrunaCore() {
2389
- if (!prunaCorePromise) {
2390
- const specifier = "@umituz/pruna-provider/core";
2391
- prunaCorePromise = import(specifier);
2556
+ // src/providers/pruna/client/core/constants/limits.ts
2557
+ var DEFAULT_ASPECT_RATIO = "16:9";
2558
+ var P_VIDEO_DEFAULTS = {
2559
+ duration: 5,
2560
+ resolution: "720p",
2561
+ fps: 24,
2562
+ draft: false,
2563
+ promptUpsampling: true
2564
+ };
2565
+ var POLL_DEFAULTS = {
2566
+ intervalMs: 3e3,
2567
+ maxAttempts: 120,
2568
+ maxIntervalMs: 1e4,
2569
+ backoffBase: 1.5,
2570
+ jitterPercent: 0.1
2571
+ };
2572
+ var DEFAULT_DEDUPLICATOR_TTL_MS = 3e4;
2573
+ var DEDUPLICATOR_CLEANUP_GRACE_MS = 100;
2574
+
2575
+ // src/providers/pruna/client/core/constants/network.ts
2576
+ var HTTPS_PREFIX = "https://";
2577
+ var HTTP_PREFIX = "http://";
2578
+ var BASE64_MARKER = "base64,";
2579
+ var PRUNA_BASE_URL = "https://api.pruna.ai";
2580
+ var PRUNA_PREDICTIONS_URL = `${PRUNA_BASE_URL}/v1/predictions`;
2581
+ var PRUNA_FILES_URL = `${PRUNA_BASE_URL}/v1/files`;
2582
+ var AUTH_FAILURE_STATUSES = [401, 403];
2583
+ var MIN_API_KEY_LENGTH = 1;
2584
+ var MAX_API_KEY_LENGTH = 256;
2585
+
2586
+ // src/providers/pruna/client/core/constants/mime-types.ts
2587
+ var MIME_PNG = "image/png";
2588
+ var MIME_JPEG = "image/jpeg";
2589
+ var MIME_WEBP = "image/webp";
2590
+ var DEFAULT_IMAGE_MIME = MIME_PNG;
2591
+ var MIME_FILE_EXTENSION = {
2592
+ [MIME_PNG]: "png",
2593
+ [MIME_JPEG]: "jpg",
2594
+ [MIME_WEBP]: "webp"
2595
+ };
2596
+ var IMAGE_SIGNATURES = {
2597
+ PNG: [137, 80],
2598
+ JPEG: [255, 216],
2599
+ WEBP_RIFF: [82, 73]
2600
+ };
2601
+
2602
+ // src/providers/pruna/client/core/errors/cancellation-error.ts
2603
+ var CANCELLATION_MESSAGE = "Request cancelled by user";
2604
+ var CancellationError = class extends Error {
2605
+ constructor(message = CANCELLATION_MESSAGE) {
2606
+ super(message);
2607
+ this.name = "CancellationError";
2608
+ }
2609
+ };
2610
+
2611
+ // src/providers/pruna/client/core/errors/authentication-error.ts
2612
+ var AUTHENTICATION_MESSAGE = "Authentication failed. Please check your API key.";
2613
+ var AuthenticationError = class extends Error {
2614
+ constructor(message = AUTHENTICATION_MESSAGE) {
2615
+ super(message);
2616
+ this.name = "AuthenticationError";
2617
+ }
2618
+ };
2619
+
2620
+ // src/providers/pruna/client/core/errors/timeout-error.ts
2621
+ var TIMEOUT_MESSAGE = "Generation timed out. Maximum polling attempts reached.";
2622
+ var TimeoutError = class extends Error {
2623
+ constructor(message = TIMEOUT_MESSAGE) {
2624
+ super(message);
2625
+ this.name = "TimeoutError";
2392
2626
  }
2393
- return prunaCorePromise;
2627
+ };
2628
+
2629
+ // src/providers/pruna/client/core/errors/validation-error.ts
2630
+ var ValidationError = class extends Error {
2631
+ field;
2632
+ reason;
2633
+ receivedValue;
2634
+ constructor(context) {
2635
+ super(`Invalid ${context.field}: ${context.reason}`);
2636
+ this.name = "ValidationError";
2637
+ this.field = context.field;
2638
+ this.reason = context.reason;
2639
+ this.receivedValue = context.receivedValue;
2640
+ }
2641
+ };
2642
+
2643
+ // src/providers/pruna/client/core/utils/base64.ts
2644
+ var globalScope = globalThis;
2645
+ var hasBuffer = typeof globalScope.Buffer?.from === "function";
2646
+ function base64ToBytes(raw) {
2647
+ if (hasBuffer) {
2648
+ const bufferFactory = globalScope.Buffer;
2649
+ if (!bufferFactory) {
2650
+ throw new Error("Buffer unavailable despite detection flag");
2651
+ }
2652
+ const buf = bufferFactory.from(raw, "base64");
2653
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
2654
+ }
2655
+ const decoded = atob(raw);
2656
+ return Uint8Array.from(decoded, (char) => char.charCodeAt(0));
2394
2657
  }
2395
- async function* generateWithPruna(request, apiKey, signal, onProgress) {
2396
- const prunaCore = await loadPrunaCore();
2397
- if (prunaCore.generateWithPruna) {
2398
- yield* prunaCore.generateWithPruna(request, apiKey, signal, onProgress);
2399
- return;
2400
- }
2401
- if (!prunaCore.submitPrediction || !prunaCore.pollForResult) {
2402
- throw new Error("@umituz/pruna-provider/core does not export required functions");
2403
- }
2404
- const modelId = request.model || (request.image ? "p-image-edit" : "p-image");
2405
- const prediction = await prunaCore.submitPrediction(modelId, request, apiKey, signal, onProgress);
2406
- if (prediction.output && typeof prediction.output === "object" && "url" in prediction.output) {
2407
- yield {
2408
- stage: "predicting",
2409
- result: { url: prediction.output.url, model: modelId }
2658
+
2659
+ // src/providers/pruna/client/core/utils/mime-detector.ts
2660
+ var MIN_SIGNATURE_BYTES = 2;
2661
+ function matchesSignature(bytes, signature) {
2662
+ if (bytes.length < signature.length) return false;
2663
+ return signature.every((value, index) => bytes[index] === value);
2664
+ }
2665
+ function detectImageMime(bytes) {
2666
+ if (bytes.length < MIN_SIGNATURE_BYTES) return DEFAULT_IMAGE_MIME;
2667
+ if (matchesSignature(bytes, IMAGE_SIGNATURES.PNG)) return MIME_PNG;
2668
+ if (matchesSignature(bytes, IMAGE_SIGNATURES.JPEG)) return MIME_JPEG;
2669
+ if (matchesSignature(bytes, IMAGE_SIGNATURES.WEBP_RIFF)) return MIME_WEBP;
2670
+ return DEFAULT_IMAGE_MIME;
2671
+ }
2672
+
2673
+ // src/providers/pruna/client/core/utils/api-error-parser.ts
2674
+ async function parseApiError(response, fallback) {
2675
+ const payload = await response.json().catch(() => ({ message: response.statusText }));
2676
+ const message = payload.message ?? `${fallback}: ${response.status}`;
2677
+ const error = new Error(message);
2678
+ error.statusCode = response.status;
2679
+ return error;
2680
+ }
2681
+
2682
+ // src/providers/pruna/client/core/protocol/http-status.ts
2683
+ function isAuthFailureStatus(status) {
2684
+ return AUTH_FAILURE_STATUSES.includes(status);
2685
+ }
2686
+
2687
+ // src/providers/pruna/client/core/protocol/job-status.ts
2688
+ var TERMINAL_SUCCESS_STATUSES = ["succeeded", "completed"];
2689
+ var TERMINAL_FAILURE_STATUSES = ["failed"];
2690
+ function isTerminalSuccessStatus(status) {
2691
+ if (!status) return false;
2692
+ return TERMINAL_SUCCESS_STATUSES.includes(status);
2693
+ }
2694
+ function isTerminalFailureStatus(status) {
2695
+ if (!status) return false;
2696
+ return TERMINAL_FAILURE_STATUSES.includes(status);
2697
+ }
2698
+
2699
+ // src/providers/pruna/client/core/protocol/abort-signal-classifier.ts
2700
+ function isAbortError(err) {
2701
+ if (err instanceof DOMException && err.code === DOMException.ABORT_ERR) return true;
2702
+ if (err instanceof Error && err.name === "AbortError") return true;
2703
+ return false;
2704
+ }
2705
+
2706
+ // src/providers/pruna/client/core/protocol/pruna-url-builder.ts
2707
+ function isAbsoluteHttpUrl(value) {
2708
+ return value.startsWith(HTTPS_PREFIX) || value.startsWith(HTTP_PREFIX);
2709
+ }
2710
+ function isHttpsUrl(value) {
2711
+ return value.startsWith(HTTPS_PREFIX);
2712
+ }
2713
+ function buildPrunaApiUrl(pathOrUrl) {
2714
+ if (isAbsoluteHttpUrl(pathOrUrl)) return pathOrUrl;
2715
+ return `${PRUNA_BASE_URL}${pathOrUrl}`;
2716
+ }
2717
+ function buildFileUrlById(fileId) {
2718
+ return `${PRUNA_FILES_URL}/${fileId}`;
2719
+ }
2720
+
2721
+ // src/providers/pruna/client/core/math/exponential-backoff.ts
2722
+ function calculateExponentialBackoff2(inputs) {
2723
+ const {
2724
+ attempt,
2725
+ baseIntervalMs,
2726
+ maxIntervalMs = POLL_DEFAULTS.maxIntervalMs,
2727
+ jitterPercent = POLL_DEFAULTS.jitterPercent,
2728
+ growthBase = POLL_DEFAULTS.backoffBase
2729
+ } = inputs;
2730
+ const safeAttempt = Math.max(0, attempt);
2731
+ const safeBase = Math.max(0, baseIntervalMs);
2732
+ const exponentialDelay = Math.min(safeBase * Math.pow(growthBase, safeAttempt), maxIntervalMs);
2733
+ const jitter = exponentialDelay * jitterPercent * Math.random();
2734
+ return exponentialDelay + jitter;
2735
+ }
2736
+
2737
+ // src/providers/pruna/client/core/utils/uri-resolver.ts
2738
+ var uriCache = /* @__PURE__ */ new WeakMap();
2739
+ function stripBase64Prefix(image) {
2740
+ if (isHttpsUrl(image)) return image;
2741
+ const base64Index = image.indexOf(BASE64_MARKER);
2742
+ return base64Index !== -1 ? image.slice(base64Index + BASE64_MARKER.length) : image;
2743
+ }
2744
+ function readOutput(output) {
2745
+ if (typeof output === "string") return output;
2746
+ if (Array.isArray(output) && output.length > 0) return output[0] ?? null;
2747
+ if (output !== null && typeof output === "object" && "url" in output) {
2748
+ return output.url;
2749
+ }
2750
+ return null;
2751
+ }
2752
+ function extractUri(data) {
2753
+ const cached = uriCache.get(data);
2754
+ if (cached !== void 0) return cached;
2755
+ if (!isTerminalSuccessStatus(data.status)) {
2756
+ uriCache.set(data, null);
2757
+ return null;
2758
+ }
2759
+ const result = data.generation_url ?? readOutput(data.output) ?? data.video_url ?? data.data ?? null;
2760
+ uriCache.set(data, result);
2761
+ return result;
2762
+ }
2763
+ function resolveUri(uri) {
2764
+ return uri.startsWith("/") ? `${PRUNA_BASE_URL}${uri}` : uri;
2765
+ }
2766
+
2767
+ // src/providers/pruna/client/core/services/pruna-client.service.ts
2768
+ function buildFileExtension(mime) {
2769
+ return MIME_FILE_EXTENSION[mime] ?? "bin";
2770
+ }
2771
+ async function uploadImage(base64OrUrl, apiKey, onProgress) {
2772
+ if (isHttpsUrl(base64OrUrl)) return base64OrUrl;
2773
+ if (base64OrUrl.startsWith(HTTP_PREFIX)) {
2774
+ throw new Error("Only HTTPS URLs are supported for security.");
2775
+ }
2776
+ onProgress?.("uploading");
2777
+ const raw = stripBase64Prefix(base64OrUrl);
2778
+ let bytes;
2779
+ try {
2780
+ bytes = base64ToBytes(raw);
2781
+ } catch {
2782
+ throw new Error("Invalid image format. Provide base64 or a valid HTTPS URL.");
2783
+ }
2784
+ const mime = detectImageMime(bytes);
2785
+ const buffer = bytes.buffer.slice(
2786
+ bytes.byteOffset,
2787
+ bytes.byteOffset + bytes.byteLength
2788
+ );
2789
+ const blob = new Blob([buffer], { type: mime });
2790
+ const formData = new FormData();
2791
+ formData.append("content", blob, `upload.${buildFileExtension(mime)}`);
2792
+ const res = await fetch(PRUNA_FILES_URL, {
2793
+ method: "POST",
2794
+ headers: { apikey: apiKey },
2795
+ body: formData
2796
+ });
2797
+ if (!res.ok) {
2798
+ throw await parseApiError(res, "File upload error");
2799
+ }
2800
+ const data = await res.json();
2801
+ return data.urls?.get ?? buildFileUrlById(data.id ?? "");
2802
+ }
2803
+ async function submitPrediction(model, input, apiKey, signal, onProgress) {
2804
+ onProgress?.("predicting");
2805
+ const res = await fetch(PRUNA_PREDICTIONS_URL, {
2806
+ method: "POST",
2807
+ headers: {
2808
+ apikey: apiKey,
2809
+ Model: model,
2810
+ "Try-Sync": "true",
2811
+ "Content-Type": "application/json"
2812
+ },
2813
+ body: JSON.stringify({ input }),
2814
+ signal
2815
+ });
2816
+ if (!res.ok) {
2817
+ throw await parseApiError(res, "API error");
2818
+ }
2819
+ return res.json();
2820
+ }
2821
+ var GenerationFailedError = class extends Error {
2822
+ constructor(message) {
2823
+ super(message);
2824
+ this.name = "GenerationFailedError";
2825
+ }
2826
+ };
2827
+ async function attemptPoll(fullUrl, apiKey, signal) {
2828
+ let res;
2829
+ try {
2830
+ res = await fetch(fullUrl, { headers: { apikey: apiKey }, signal });
2831
+ } catch (err) {
2832
+ if (isAbortError(err)) throw new CancellationError();
2833
+ return { outcome: "transient-failure" };
2834
+ }
2835
+ if (!res.ok) {
2836
+ if (isAuthFailureStatus(res.status)) throw new AuthenticationError();
2837
+ return { outcome: "transient-failure" };
2838
+ }
2839
+ let data;
2840
+ try {
2841
+ data = await res.json();
2842
+ } catch {
2843
+ return { outcome: "transient-failure" };
2844
+ }
2845
+ if (isTerminalSuccessStatus(data.status)) {
2846
+ const uri = extractUri(data);
2847
+ if (uri) return { outcome: "succeeded", uri: resolveUri(uri) };
2848
+ return { outcome: "transient-failure" };
2849
+ }
2850
+ if (isTerminalFailureStatus(data.status)) {
2851
+ throw new GenerationFailedError(data.error ?? "Generation failed during processing.");
2852
+ }
2853
+ return { outcome: "continue" };
2854
+ }
2855
+ async function pollForResult(pollUrl, apiKey, maxAttempts = POLL_DEFAULTS.maxAttempts, intervalMs = POLL_DEFAULTS.intervalMs, signal, onProgress) {
2856
+ const fullUrl = isAbsoluteHttpUrl(pollUrl) ? pollUrl : buildPrunaApiUrl(pollUrl);
2857
+ const controller = new AbortController();
2858
+ const onExternalAbort = () => controller.abort();
2859
+ signal?.addEventListener("abort", onExternalAbort);
2860
+ try {
2861
+ for (let i = 0; i < maxAttempts; i++) {
2862
+ if (signal?.aborted) throw new CancellationError();
2863
+ const delay = calculateExponentialBackoff2({ attempt: i, baseIntervalMs: intervalMs });
2864
+ await new Promise((resolve) => setTimeout(resolve, delay));
2865
+ if (signal?.aborted) throw new CancellationError();
2866
+ onProgress?.("polling", i + 1);
2867
+ const result = await attemptPoll(fullUrl, apiKey, controller.signal);
2868
+ if (result.outcome === "succeeded" && result.uri) {
2869
+ return result.uri;
2870
+ }
2871
+ }
2872
+ throw new TimeoutError();
2873
+ } finally {
2874
+ signal?.removeEventListener("abort", onExternalAbort);
2875
+ controller.abort();
2876
+ }
2877
+ }
2878
+
2879
+ // src/providers/pruna/client/core/math/comparison-hash.ts
2880
+ function sortKeysDeep(value) {
2881
+ if (Array.isArray(value)) {
2882
+ return value.map(sortKeysDeep);
2883
+ }
2884
+ if (value !== null && typeof value === "object") {
2885
+ const record = value;
2886
+ return Object.keys(record).sort().reduce((acc, key) => {
2887
+ acc[key] = sortKeysDeep(record[key]);
2888
+ return acc;
2889
+ }, {});
2890
+ }
2891
+ return value;
2892
+ }
2893
+ function hashValue(value) {
2894
+ if (value === null || typeof value !== "object") {
2895
+ return String(value);
2896
+ }
2897
+ try {
2898
+ return JSON.stringify(sortKeysDeep(value));
2899
+ } catch {
2900
+ return String(value);
2901
+ }
2902
+ }
2903
+ function compositeHash(...values) {
2904
+ return values.map(hashValue).join("|");
2905
+ }
2906
+
2907
+ // src/providers/pruna/client/core/math/cache-key-derivation.ts
2908
+ function deriveGenerationCacheKey(apiKey, input) {
2909
+ return `${apiKey}:${compositeHash(input)}`;
2910
+ }
2911
+
2912
+ // src/providers/pruna/client/core/validation/api-key-validator.ts
2913
+ function assertValidApiKey(apiKey) {
2914
+ if (typeof apiKey !== "string") {
2915
+ throw new ValidationError({
2916
+ field: "apiKey",
2917
+ reason: "must be a string",
2918
+ receivedValue: typeof apiKey
2919
+ });
2920
+ }
2921
+ if (apiKey.length < MIN_API_KEY_LENGTH) {
2922
+ throw new ValidationError({
2923
+ field: "apiKey",
2924
+ reason: "must not be empty",
2925
+ receivedValue: apiKey
2926
+ });
2927
+ }
2928
+ if (apiKey.length > MAX_API_KEY_LENGTH) {
2929
+ throw new ValidationError({
2930
+ field: "apiKey",
2931
+ reason: `exceeds maximum length of ${MAX_API_KEY_LENGTH}`,
2932
+ receivedValue: apiKey.length
2933
+ });
2934
+ }
2935
+ }
2936
+
2937
+ // src/providers/pruna/client/core/validation/image-source-validator.ts
2938
+ function classifyImageSource(source) {
2939
+ if (source.startsWith(HTTPS_PREFIX)) return "https-url";
2940
+ if (source.startsWith(HTTP_PREFIX)) return "http-url";
2941
+ if (source.includes(BASE64_MARKER) || /^[A-Za-z0-9+/=]+$/.test(source)) return "base64";
2942
+ return "unknown";
2943
+ }
2944
+ function assertSecureImageSource(image, fieldName = "image") {
2945
+ if (typeof image !== "string") {
2946
+ throw new ValidationError({
2947
+ field: fieldName,
2948
+ reason: "must be a string",
2949
+ receivedValue: typeof image
2950
+ });
2951
+ }
2952
+ const kind = classifyImageSource(image);
2953
+ if (kind === "http-url") {
2954
+ throw new ValidationError({
2955
+ field: fieldName,
2956
+ reason: "HTTP URLs are not supported; use HTTPS or a base64 data URI",
2957
+ receivedValue: image
2958
+ });
2959
+ }
2960
+ if (kind === "unknown") {
2961
+ throw new ValidationError({
2962
+ field: fieldName,
2963
+ reason: "must be an HTTPS URL or a base64 data URI",
2964
+ receivedValue: typeof image
2965
+ });
2966
+ }
2967
+ }
2968
+
2969
+ // src/providers/pruna/client/core/validation/aspect-ratio-validator.ts
2970
+ var SUPPORTED_ASPECT_RATIOS = [
2971
+ "16:9",
2972
+ "9:16",
2973
+ "1:1",
2974
+ "4:3",
2975
+ "3:4",
2976
+ "3:2",
2977
+ "2:3"
2978
+ ];
2979
+ function isSupportedAspectRatio(value) {
2980
+ return typeof value === "string" && SUPPORTED_ASPECT_RATIOS.includes(value);
2981
+ }
2982
+ function assertValidAspectRatio(value, fieldName = "aspect_ratio") {
2983
+ if (value === void 0 || value === null) return;
2984
+ if (!isSupportedAspectRatio(value)) {
2985
+ throw new ValidationError({
2986
+ field: fieldName,
2987
+ reason: `must be one of ${SUPPORTED_ASPECT_RATIOS.join(", ")}`,
2988
+ receivedValue: value
2989
+ });
2990
+ }
2991
+ }
2992
+
2993
+ // src/providers/pruna/client/core/validation/resolution-validator.ts
2994
+ var SUPPORTED_RESOLUTIONS = ["720p", "1080p"];
2995
+ function isSupportedResolution(value) {
2996
+ return typeof value === "string" && SUPPORTED_RESOLUTIONS.includes(value);
2997
+ }
2998
+ function assertValidResolution(value, fieldName = "resolution") {
2999
+ if (value === void 0 || value === null) return;
3000
+ if (!isSupportedResolution(value)) {
3001
+ throw new ValidationError({
3002
+ field: fieldName,
3003
+ reason: `must be one of ${SUPPORTED_RESOLUTIONS.join(", ")}`,
3004
+ receivedValue: value
3005
+ });
3006
+ }
3007
+ }
3008
+
3009
+ // src/providers/pruna/client/core/validation/duration-validator.ts
3010
+ var MIN_VIDEO_DURATION_SECONDS = 1;
3011
+ var MAX_VIDEO_DURATION_SECONDS = 30;
3012
+ function isValidDuration(value) {
3013
+ return typeof value === "number" && Number.isFinite(value) && value >= MIN_VIDEO_DURATION_SECONDS && value <= MAX_VIDEO_DURATION_SECONDS && Number.isInteger(value);
3014
+ }
3015
+ function assertValidDuration(value, fieldName = "duration") {
3016
+ if (value === void 0 || value === null) return;
3017
+ if (!isValidDuration(value)) {
3018
+ throw new ValidationError({
3019
+ field: fieldName,
3020
+ reason: `must be an integer between ${MIN_VIDEO_DURATION_SECONDS} and ${MAX_VIDEO_DURATION_SECONDS} seconds`,
3021
+ receivedValue: value
3022
+ });
3023
+ }
3024
+ }
3025
+
3026
+ // src/providers/pruna/client/core/validation/prompt-validator.ts
3027
+ var MIN_PROMPT_LENGTH = 1;
3028
+ var MAX_PROMPT_LENGTH = 4e3;
3029
+ function assertValidPrompt(value, fieldName = "prompt") {
3030
+ if (typeof value !== "string") {
3031
+ throw new ValidationError({
3032
+ field: fieldName,
3033
+ reason: "must be a string",
3034
+ receivedValue: typeof value
3035
+ });
3036
+ }
3037
+ if (value.trim().length < MIN_PROMPT_LENGTH) {
3038
+ throw new ValidationError({
3039
+ field: fieldName,
3040
+ reason: "must not be empty or whitespace-only",
3041
+ receivedValue: value
3042
+ });
3043
+ }
3044
+ if (value.length > MAX_PROMPT_LENGTH) {
3045
+ throw new ValidationError({
3046
+ field: fieldName,
3047
+ reason: `exceeds maximum length of ${MAX_PROMPT_LENGTH} characters`,
3048
+ receivedValue: value.length
3049
+ });
3050
+ }
3051
+ }
3052
+
3053
+ // src/providers/pruna/client/core/validation/seed-validator.ts
3054
+ var MIN_SEED = -2147483648;
3055
+ var MAX_SEED = 2147483647;
3056
+ function isValidSeed(value) {
3057
+ return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= MIN_SEED && value <= MAX_SEED;
3058
+ }
3059
+ function assertValidSeed(value, fieldName = "seed") {
3060
+ if (value === void 0 || value === null) return;
3061
+ if (!isValidSeed(value)) {
3062
+ throw new ValidationError({
3063
+ field: fieldName,
3064
+ reason: `must be an integer in [${MIN_SEED}, ${MAX_SEED}]`,
3065
+ receivedValue: value
3066
+ });
3067
+ }
3068
+ }
3069
+
3070
+ // src/providers/pruna/client/core/protocol/input-type-guard.ts
3071
+ function isTextToImageInput(input) {
3072
+ return input.model === "p-image";
3073
+ }
3074
+ function isImageToImageInput(input) {
3075
+ return input.model === "p-image-edit";
3076
+ }
3077
+ function isImageToVideoInput(input) {
3078
+ return input.model === "p-video";
3079
+ }
3080
+
3081
+ // src/providers/pruna/client/core/utils/request-deduplicator.ts
3082
+ var RequestDeduplicator2 = class {
3083
+ pendingRequests = /* @__PURE__ */ new Map();
3084
+ ttl;
3085
+ constructor(ttl = DEFAULT_DEDUPLICATOR_TTL_MS) {
3086
+ this.ttl = ttl;
3087
+ }
3088
+ execute(key, fn) {
3089
+ const existing = this.pendingRequests.get(key);
3090
+ if (existing) {
3091
+ if (Date.now() - existing.timestamp < this.ttl) {
3092
+ return existing.promise;
3093
+ }
3094
+ this.pendingRequests.delete(key);
3095
+ }
3096
+ const promise = fn().finally(() => {
3097
+ setTimeout(() => {
3098
+ this.pendingRequests.delete(key);
3099
+ }, DEDUPLICATOR_CLEANUP_GRACE_MS);
3100
+ });
3101
+ this.pendingRequests.set(key, { promise, timestamp: Date.now() });
3102
+ return promise;
3103
+ }
3104
+ clear() {
3105
+ this.pendingRequests.clear();
3106
+ }
3107
+ size() {
3108
+ this.purgeExpired();
3109
+ return this.pendingRequests.size;
3110
+ }
3111
+ purgeExpired() {
3112
+ const now = Date.now();
3113
+ for (const [key, value] of this.pendingRequests) {
3114
+ if (now - value.timestamp >= this.ttl) {
3115
+ this.pendingRequests.delete(key);
3116
+ }
3117
+ }
3118
+ }
3119
+ };
3120
+ var globalDeduplicator = new RequestDeduplicator2();
3121
+
3122
+ // src/providers/pruna/client/generation/builders/text-to-image.builder.ts
3123
+ var TextToImageInputBuilder = class {
3124
+ model = "p-image";
3125
+ build(input, _context) {
3126
+ const payload = {
3127
+ prompt: input.prompt,
3128
+ aspect_ratio: input.aspect_ratio ?? DEFAULT_ASPECT_RATIO
3129
+ };
3130
+ if (input.seed !== void 0) {
3131
+ payload.seed = input.seed;
3132
+ }
3133
+ return Promise.resolve(payload);
3134
+ }
3135
+ };
3136
+
3137
+ // src/providers/pruna/client/generation/builders/image-to-image.builder.ts
3138
+ var ImageToImageInputBuilder = class {
3139
+ model = "p-image-edit";
3140
+ build(input, _context) {
3141
+ const payload = {
3142
+ images: [stripBase64Prefix(input.image)],
3143
+ prompt: input.prompt,
3144
+ aspect_ratio: input.aspect_ratio ?? DEFAULT_ASPECT_RATIO
3145
+ };
3146
+ if (input.seed !== void 0) {
3147
+ payload.seed = input.seed;
3148
+ }
3149
+ return Promise.resolve(payload);
3150
+ }
3151
+ };
3152
+
3153
+ // src/providers/pruna/client/generation/builders/image-to-video.builder.ts
3154
+ var ImageToVideoInputBuilder = class {
3155
+ model = "p-video";
3156
+ async build(input, context) {
3157
+ if (context.signal?.aborted) {
3158
+ throw new CancellationError();
3159
+ }
3160
+ const fileUrl = await uploadImage(
3161
+ input.image,
3162
+ context.apiKey,
3163
+ context.onProgress
3164
+ );
3165
+ return {
3166
+ image: fileUrl,
3167
+ prompt: input.prompt,
3168
+ duration: input.duration ?? P_VIDEO_DEFAULTS.duration,
3169
+ resolution: input.resolution ?? P_VIDEO_DEFAULTS.resolution,
3170
+ fps: P_VIDEO_DEFAULTS.fps,
3171
+ draft: input.draft ?? P_VIDEO_DEFAULTS.draft,
3172
+ aspect_ratio: input.aspect_ratio ?? DEFAULT_ASPECT_RATIO,
3173
+ prompt_upsampling: P_VIDEO_DEFAULTS.promptUpsampling
2410
3174
  };
2411
- return;
2412
3175
  }
2413
- const pollUrl = prediction.status_url || prediction.generation_url;
3176
+ };
3177
+
3178
+ // src/providers/pruna/client/generation/builders/model-input-builder.ts
3179
+ var ModelInputBuilderRegistry = class {
3180
+ builders = /* @__PURE__ */ new Map();
3181
+ register(builder) {
3182
+ this.builders.set(builder.model, builder);
3183
+ return this;
3184
+ }
3185
+ resolve(model) {
3186
+ const builder = this.builders.get(model);
3187
+ if (!builder) {
3188
+ throw new Error(`No input builder registered for model: ${model}`);
3189
+ }
3190
+ return builder;
3191
+ }
3192
+ };
3193
+
3194
+ // src/providers/pruna/client/generation/builders/builder-factory.ts
3195
+ function createDefaultBuilderRegistry() {
3196
+ return new ModelInputBuilderRegistry().register(new TextToImageInputBuilder()).register(new ImageToImageInputBuilder()).register(new ImageToVideoInputBuilder());
3197
+ }
3198
+
3199
+ // src/providers/pruna/client/generation/services/generation.service.ts
3200
+ var builderRegistry = createDefaultBuilderRegistry();
3201
+ function validateGenerateInput(input) {
3202
+ assertValidPrompt(input.prompt, `${input.model}.prompt`);
3203
+ assertValidAspectRatio(input.aspect_ratio, `${input.model}.aspect_ratio`);
3204
+ if (isTextToImageInput(input)) {
3205
+ assertValidSeed(input.seed, `${input.model}.seed`);
3206
+ } else if (isImageToImageInput(input)) {
3207
+ assertSecureImageSource(input.image, `${input.model}.image`);
3208
+ assertValidSeed(input.seed, `${input.model}.seed`);
3209
+ } else if (isImageToVideoInput(input)) {
3210
+ assertSecureImageSource(input.image, `${input.model}.image`);
3211
+ assertValidDuration(input.duration, `${input.model}.duration`);
3212
+ assertValidResolution(input.resolution, `${input.model}.resolution`);
3213
+ }
3214
+ }
3215
+ async function generate(apiKey, input, options = {}) {
3216
+ assertValidApiKey(apiKey);
3217
+ validateGenerateInput(input);
3218
+ const { signal, onProgress, useCache = true } = options;
3219
+ if (useCache) {
3220
+ const cacheKey = deriveGenerationCacheKey(apiKey, input);
3221
+ return globalDeduplicator.execute(
3222
+ cacheKey,
3223
+ () => generateInternal(apiKey, input, { signal, onProgress })
3224
+ );
3225
+ }
3226
+ return generateInternal(apiKey, input, { signal, onProgress });
3227
+ }
3228
+ async function generateInternal(apiKey, input, options) {
3229
+ const { signal, onProgress } = options;
3230
+ const builder = builderRegistry.resolve(input.model);
3231
+ const modelInput = await builder.build(input, { apiKey, signal, onProgress });
3232
+ const response = await submitPrediction(input.model, modelInput, apiKey, signal, onProgress);
3233
+ const syncUri = extractUri(response);
3234
+ if (syncUri) {
3235
+ return { url: resolveUri(syncUri), model: input.model };
3236
+ }
3237
+ const pollUrl = response.get_url ?? response.status_url;
2414
3238
  if (!pollUrl) {
2415
- throw new Error("Pruna prediction response missing polling URL");
3239
+ throw new Error("Pruna API returned no result and no polling URL.");
2416
3240
  }
2417
- const resultUrl = await prunaCore.pollForResult(pollUrl, apiKey, 60, 2e3, signal, onProgress);
2418
- yield { stage: "polling", result: { url: resultUrl, model: modelId } };
3241
+ const url = await pollForResult(
3242
+ pollUrl,
3243
+ apiKey,
3244
+ POLL_DEFAULTS.maxAttempts,
3245
+ POLL_DEFAULTS.intervalMs,
3246
+ signal,
3247
+ onProgress
3248
+ );
3249
+ return { url, model: input.model };
3250
+ }
3251
+ async function generateImageThenVideo(apiKey, input, options = {}) {
3252
+ assertValidApiKey(apiKey);
3253
+ assertValidPrompt(input.prompt, "text-to-video.prompt");
3254
+ assertValidAspectRatio(input.aspect_ratio, "text-to-video.aspect_ratio");
3255
+ assertValidDuration(input.duration, "text-to-video.duration");
3256
+ assertValidResolution(input.resolution, "text-to-video.resolution");
3257
+ const { signal, onProgress } = options;
3258
+ const aspectRatio = input.aspect_ratio ?? DEFAULT_ASPECT_RATIO;
3259
+ onProgress?.("predicting", 1);
3260
+ const imageResult = await generate(
3261
+ apiKey,
3262
+ { model: "p-image", prompt: input.prompt, aspect_ratio: aspectRatio },
3263
+ { signal }
3264
+ );
3265
+ onProgress?.("predicting", 2);
3266
+ return generate(
3267
+ apiKey,
3268
+ {
3269
+ model: "p-video",
3270
+ prompt: input.prompt,
3271
+ image: imageResult.url,
3272
+ duration: input.duration ?? P_VIDEO_DEFAULTS.duration,
3273
+ resolution: input.resolution ?? P_VIDEO_DEFAULTS.resolution,
3274
+ aspect_ratio: aspectRatio,
3275
+ draft: input.draft ?? P_VIDEO_DEFAULTS.draft
3276
+ },
3277
+ { signal, onProgress }
3278
+ );
3279
+ }
3280
+
3281
+ // src/providers/pruna/pruna-generate.ts
3282
+ async function* generateWithPruna(request, apiKey, signal, onProgress) {
3283
+ const stages = [];
3284
+ let notify = null;
3285
+ let done = false;
3286
+ const wake = () => {
3287
+ const fn = notify;
3288
+ if (fn) {
3289
+ notify = null;
3290
+ fn();
3291
+ }
3292
+ };
3293
+ const handleProgress = (stage) => {
3294
+ stages.push({ stage });
3295
+ onProgress?.(stage);
3296
+ wake();
3297
+ };
3298
+ const promise = (async () => {
3299
+ try {
3300
+ const model = request.model ?? (request.image ? "p-image-edit" : "p-image");
3301
+ if (model === "p-video" && !request.image && request.prompt) {
3302
+ const videoRes = await generateImageThenVideo(
3303
+ apiKey,
3304
+ {
3305
+ prompt: request.prompt,
3306
+ duration: request.duration ?? 5,
3307
+ aspect_ratio: request.aspectRatio ?? request.aspect_ratio ?? "16:9"
3308
+ },
3309
+ { signal, onProgress: handleProgress }
3310
+ );
3311
+ stages.push({
3312
+ stage: "polling",
3313
+ result: { url: videoRes.url, model: "p-video" }
3314
+ });
3315
+ return;
3316
+ }
3317
+ const input = {
3318
+ model,
3319
+ prompt: request.prompt ?? "",
3320
+ aspect_ratio: request.aspectRatio ?? request.aspect_ratio ?? "1:1",
3321
+ ...request.image ? { image: request.image } : {},
3322
+ ...request.duration ? { duration: request.duration } : {},
3323
+ ...request.resolution ? { resolution: request.resolution } : {},
3324
+ ...request.seed !== void 0 ? { seed: request.seed } : {}
3325
+ };
3326
+ const res = await generate(apiKey, input, {
3327
+ signal,
3328
+ onProgress: handleProgress
3329
+ });
3330
+ stages.push({
3331
+ stage: "polling",
3332
+ result: { url: res.url, model: res.model }
3333
+ });
3334
+ } catch (err) {
3335
+ stages.push({
3336
+ stage: "processing",
3337
+ error: err instanceof Error ? err : new Error(String(err))
3338
+ });
3339
+ } finally {
3340
+ done = true;
3341
+ wake();
3342
+ }
3343
+ })();
3344
+ while (!done || stages.length > 0) {
3345
+ if (stages.length === 0 && !done) {
3346
+ await new Promise((resolve) => {
3347
+ notify = resolve;
3348
+ });
3349
+ notify = null;
3350
+ }
3351
+ while (stages.length > 0) {
3352
+ const nextStage = stages.shift();
3353
+ if (!nextStage) continue;
3354
+ yield nextStage;
3355
+ if (nextStage.error) {
3356
+ throw nextStage.error;
3357
+ }
3358
+ }
3359
+ }
3360
+ await promise;
2419
3361
  }
2420
3362
 
2421
3363
  // src/providers/pruna/pruna.provider.ts
@@ -2528,6 +3470,504 @@ function createAI(config = {}) {
2528
3470
  return factory;
2529
3471
  }
2530
3472
 
2531
- export { ANTHROPIC_API_VERSION, ANTHROPIC_DEFAULT_MAX_TOKENS, ANTHROPIC_MODELS, ANTHROPIC_TIERS, AnthropicHttpTransport, AnthropicProvider, AnthropicRequestBuilder, BaseProvider, FalProvider, GEMINI_TIERS, GROQ_MODELS, GROQ_TIERS, GeminiProvider, GroqChatRequestBuilder, GroqHttpClient, GroqHttpTransport, GroqProvider, ProviderError, ProviderFactory, PrunaProvider, consumeAnthropicStream, consumeGroqStream, createAI, createFalProvider, createGeminiProvider, createPrunaProvider, defaultEffortForModel, extractContentFromSsePayload, generateWithPruna, isMediaProvider, isTextProvider, mapAnthropicStopReason, toGroqMessages };
3473
+ // src/providers/groq/groq-messages.ts
3474
+ var createUserMessage = (content) => ({
3475
+ role: "user",
3476
+ content
3477
+ });
3478
+ var createAssistantMessage = (content) => ({
3479
+ role: "assistant",
3480
+ content
3481
+ });
3482
+ var createSystemMessage = (content) => ({
3483
+ role: "system",
3484
+ content
3485
+ });
3486
+ var createMessage = (role, content) => ({
3487
+ role,
3488
+ content
3489
+ });
3490
+ var promptToMessages = (prompt) => [
3491
+ createUserMessage(prompt)
3492
+ ];
3493
+ var extractTextFromMessages = (messages) => messages.map((m) => m.content).join("\n\n");
3494
+ var formatMessagesForDisplay = (messages) => messages.map((m) => `[${m.role.toUpperCase()}]: ${m.content}`).join("\n\n---\n\n");
3495
+
3496
+ // src/providers/groq/groq-structured-json.ts
3497
+ async function requestStructuredJson(client, request) {
3498
+ const chatRequest = {
3499
+ model: request.model,
3500
+ messages: [createUserMessage(request.instructions)],
3501
+ temperature: request.temperature,
3502
+ max_tokens: request.maxTokens,
3503
+ response_format: { type: "json_object" },
3504
+ ...request.reasoningEffort !== void 0 ? { reasoning_effort: request.reasoningEffort } : {}
3505
+ };
3506
+ const response = await client.postChatCompletion(chatRequest);
3507
+ const content = response.choices[0]?.message?.content ?? "";
3508
+ const candidate = extractJsonCandidate(content);
3509
+ try {
3510
+ return JSON.parse(candidate);
3511
+ } catch (err) {
3512
+ throw new GroqError(
3513
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
3514
+ "model response contained no parsable json object",
3515
+ err instanceof Error ? err : void 0
3516
+ );
3517
+ }
3518
+ }
3519
+
3520
+ // src/providers/groq/text-generation.service.ts
3521
+ var STRUCTURED_OUTPUT_TEMPERATURE = 0.1;
3522
+ var TextGenerationService = class {
3523
+ constructor(httpClient, cache = new CacheManager(), defaultModel = DEFAULT_MODELS.TEXT) {
3524
+ this.httpClient = httpClient;
3525
+ this.cache = cache;
3526
+ this.defaultModel = defaultModel;
3527
+ }
3528
+ httpClient;
3529
+ cache;
3530
+ defaultModel;
3531
+ id = "groq-text-generation";
3532
+ name = "Groq Text Generation";
3533
+ capabilities = ["text"];
3534
+ jsonParser = new JsonResponseParser();
3535
+ generateCompletion(prompt, options = {}) {
3536
+ const model = options.model ?? this.defaultModel;
3537
+ return this.executeWithCache(
3538
+ {
3539
+ type: CACHE_KEY_TYPE_COMPLETION,
3540
+ model,
3541
+ prompt,
3542
+ config: options.generationConfig
3543
+ },
3544
+ async () => {
3545
+ const request = GroqChatRequestBuilder.for(model).withMessages([createUserMessage(prompt)]).withConfig(options.generationConfig).build();
3546
+ const response = await this.httpClient.postChatCompletion(request);
3547
+ return this.extractContent(response.choices[0]?.message?.content);
3548
+ }
3549
+ );
3550
+ }
3551
+ generateChatCompletion(messages, options = {}) {
3552
+ const model = options.model ?? this.defaultModel;
3553
+ return this.executeWithCache(
3554
+ {
3555
+ type: CACHE_KEY_TYPE_CHAT,
3556
+ model,
3557
+ messages: [...messages],
3558
+ config: options.generationConfig
3559
+ },
3560
+ async () => {
3561
+ const request = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig(options.generationConfig).build();
3562
+ const response = await this.httpClient.postChatCompletion(request);
3563
+ return this.extractContent(response.choices[0]?.message?.content);
3564
+ }
3565
+ );
3566
+ }
3567
+ generateStructured(promptOrRequest, options) {
3568
+ if (typeof promptOrRequest === "string") {
3569
+ const model2 = options?.model ?? this.defaultModel;
3570
+ const jsonPrompt = StructuredPromptBuilder.for(promptOrRequest).withSchema(options?.schema).build();
3571
+ const request = GroqChatRequestBuilder.for(model2).withMessages([createUserMessage(jsonPrompt)]).withConfig({
3572
+ ...options?.generationConfig,
3573
+ temperature: STRUCTURED_OUTPUT_TEMPERATURE
3574
+ }).build();
3575
+ return this.httpClient.postChatCompletion(request).then((response) => {
3576
+ const content = this.extractContent(response.choices[0]?.message?.content);
3577
+ return this.jsonParser.parse(content);
3578
+ });
3579
+ }
3580
+ const req = promptOrRequest;
3581
+ const model = req.model ?? (req.tier ? this.resolveModel(req.tier) : this.defaultModel);
3582
+ const schemaObj = req.schema;
3583
+ const messages = [];
3584
+ if (req.systemPrompt) {
3585
+ messages.push({ role: "system", content: req.systemPrompt });
3586
+ }
3587
+ for (const m of req.messages) {
3588
+ messages.push({ role: m.role, content: m.content });
3589
+ }
3590
+ const lastMsg = messages[messages.length - 1];
3591
+ if (lastMsg) {
3592
+ const structuredPrompt = StructuredPromptBuilder.for(lastMsg.content).withSchema(schemaObj).build();
3593
+ messages[messages.length - 1] = { ...lastMsg, content: structuredPrompt };
3594
+ }
3595
+ const groqReq = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig({
3596
+ temperature: req.temperature ?? STRUCTURED_OUTPUT_TEMPERATURE,
3597
+ maxTokens: req.maxTokens
3598
+ }).build();
3599
+ return this.httpClient.postChatCompletion(groqReq).then((response) => {
3600
+ const content = this.extractContent(response.choices[0]?.message?.content);
3601
+ const parsed = this.jsonParser.parse(content);
3602
+ const genResult = {
3603
+ text: content,
3604
+ model: response.model,
3605
+ providerId: this.id,
3606
+ finishReason: response.choices[0]?.finish_reason ?? "stop",
3607
+ usage: response.usage ? {
3608
+ inputTokens: response.usage.prompt_tokens,
3609
+ outputTokens: response.usage.completion_tokens,
3610
+ totalTokens: response.usage.total_tokens
3611
+ } : void 0
3612
+ };
3613
+ return { value: parsed, result: genResult };
3614
+ });
3615
+ }
3616
+ streamCompletion(prompt, callbacks, options = {}) {
3617
+ return this.streamChatCompletion([createUserMessage(prompt)], callbacks, options);
3618
+ }
3619
+ async streamChatCompletion(messages, callbacks, options = {}) {
3620
+ const model = options.model ?? this.defaultModel;
3621
+ const request = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig(options.generationConfig).withStream(true).build();
3622
+ const parser = new StreamingResponseParser(
3623
+ {
3624
+ onChunk: (chunk) => callbacks.onChunk?.(chunk)
3625
+ },
3626
+ extractContentFromSsePayload
3627
+ );
3628
+ try {
3629
+ const stream = await this.httpClient.streamChatCompletion(request, {
3630
+ signal: options.signal
3631
+ });
3632
+ const fullText = await parser.parse(stream);
3633
+ callbacks.onComplete?.(fullText);
3634
+ return fullText;
3635
+ } catch (error) {
3636
+ const err = error instanceof Error ? error : new Error(String(error));
3637
+ callbacks.onError?.(err);
3638
+ throw err;
3639
+ }
3640
+ }
3641
+ clearCache() {
3642
+ this.cache.clear();
3643
+ }
3644
+ // --- ITextProvider implementation ---
3645
+ resolveModel(tier) {
3646
+ return GROQ_TIERS[tier] ?? this.defaultModel;
3647
+ }
3648
+ async generate(request) {
3649
+ const messages = [];
3650
+ if (request.systemPrompt) {
3651
+ messages.push({ role: "system", content: request.systemPrompt });
3652
+ }
3653
+ for (const m of request.messages) {
3654
+ messages.push({ role: m.role, content: m.content });
3655
+ }
3656
+ const model = request.model ?? (request.tier ? this.resolveModel(request.tier) : this.defaultModel);
3657
+ const groqReq = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig({
3658
+ temperature: request.temperature,
3659
+ maxTokens: request.maxTokens
3660
+ }).build();
3661
+ const resp = await this.httpClient.postChatCompletion(groqReq);
3662
+ const content = resp.choices[0]?.message?.content ?? "";
3663
+ return {
3664
+ text: content,
3665
+ finishReason: resp.choices[0]?.finish_reason ?? "stop",
3666
+ model: resp.model,
3667
+ providerId: this.id,
3668
+ usage: resp.usage ? {
3669
+ inputTokens: resp.usage.prompt_tokens,
3670
+ outputTokens: resp.usage.completion_tokens,
3671
+ totalTokens: resp.usage.total_tokens
3672
+ } : void 0
3673
+ };
3674
+ }
3675
+ async stream(request, callbacks) {
3676
+ const messages = [];
3677
+ if (request.systemPrompt) {
3678
+ messages.push({ role: "system", content: request.systemPrompt });
3679
+ }
3680
+ for (const m of request.messages) {
3681
+ messages.push({ role: m.role, content: m.content });
3682
+ }
3683
+ const model = request.model ?? (request.tier ? this.resolveModel(request.tier) : this.defaultModel);
3684
+ const text = await this.streamChatCompletion(
3685
+ messages,
3686
+ {
3687
+ onChunk: callbacks.onChunk,
3688
+ onError: callbacks.onError
3689
+ },
3690
+ {
3691
+ model,
3692
+ signal: request.signal,
3693
+ generationConfig: {
3694
+ temperature: request.temperature,
3695
+ maxTokens: request.maxTokens
3696
+ }
3697
+ }
3698
+ );
3699
+ const result = {
3700
+ text,
3701
+ finishReason: "stop",
3702
+ model,
3703
+ providerId: this.id
3704
+ };
3705
+ callbacks.onComplete?.(result);
3706
+ return result;
3707
+ }
3708
+ async countTokens(text) {
3709
+ return Math.max(1, Math.ceil(text.length / 4));
3710
+ }
3711
+ async estimateCost(_request) {
3712
+ return 0;
3713
+ }
3714
+ async healthCheck() {
3715
+ try {
3716
+ await this.generateCompletion("ping", {
3717
+ generationConfig: { maxTokens: 1 }
3718
+ });
3719
+ return "healthy";
3720
+ } catch {
3721
+ return "unavailable";
3722
+ }
3723
+ }
3724
+ async executeWithCache(params, factory) {
3725
+ const key = this.cache.generateKey(params);
3726
+ const cached = this.cache.get(key);
3727
+ if (cached !== null) {
3728
+ return cached;
3729
+ }
3730
+ const result = await factory();
3731
+ this.cache.set(key, result);
3732
+ return result;
3733
+ }
3734
+ extractContent(content) {
3735
+ if (!content) {
3736
+ throw new GroqError(
3737
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
3738
+ "No content generated from Groq API"
3739
+ );
3740
+ }
3741
+ return content;
3742
+ }
3743
+ };
3744
+
3745
+ // src/core/calculations/random-base36-suffix.ts
3746
+ var BASE_36_RADIX = 36;
3747
+ var DECIMAL_OFFSET = 2;
3748
+ function generateRandomBase36Suffix(length, random = Math.random) {
3749
+ if (length <= 0) return "";
3750
+ const raw = random().toString(BASE_36_RADIX).slice(DECIMAL_OFFSET);
3751
+ if (raw.length >= length) return raw.slice(0, length);
3752
+ let result = raw;
3753
+ while (result.length < length) {
3754
+ result += random().toString(BASE_36_RADIX).slice(DECIMAL_OFFSET);
3755
+ }
3756
+ return result.slice(0, length);
3757
+ }
3758
+
3759
+ // src/chat/utils/chat-id-generator.ts
3760
+ var ID_PART_DELIMITER = "-";
3761
+ var ChatIdGenerator = class {
3762
+ counter = 0;
3763
+ randomLength;
3764
+ constructor(randomLength = CHAT_MESSAGE_ID_SUFFIX_LENGTH) {
3765
+ this.randomLength = randomLength;
3766
+ }
3767
+ next(sender) {
3768
+ this.counter += 1;
3769
+ const random = generateRandomBase36Suffix(this.randomLength);
3770
+ return [
3771
+ MESSAGE_ID_PREFIX,
3772
+ Date.now(),
3773
+ sender,
3774
+ this.counter,
3775
+ random
3776
+ ].join(ID_PART_DELIMITER);
3777
+ }
3778
+ };
3779
+ var chatIdGenerator = new ChatIdGenerator();
3780
+
3781
+ // src/chat/utils/message-formatter.ts
3782
+ var MessageFormatter = class {
3783
+ constructor(idGenerator = chatIdGenerator) {
3784
+ this.idGenerator = idGenerator;
3785
+ }
3786
+ idGenerator;
3787
+ /** Maps chat-domain history to neutral core messages; system senders are
3788
+ * dropped — the system prompt travels on the request's own slot. */
3789
+ toCoreMessages(messages) {
3790
+ const coreMessages = [];
3791
+ for (const message of messages) {
3792
+ if (message.sender === "system") continue;
3793
+ coreMessages.push({
3794
+ role: message.sender === "user" ? USER_ROLE : ASSISTANT_ROLE,
3795
+ content: message.content
3796
+ });
3797
+ }
3798
+ return coreMessages;
3799
+ }
3800
+ toChatMessage(content, sender) {
3801
+ return {
3802
+ id: this.idGenerator.next(sender),
3803
+ sender,
3804
+ content: content.trim(),
3805
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3806
+ type: "text"
3807
+ };
3808
+ }
3809
+ };
3810
+ var messageFormatter = new MessageFormatter();
3811
+
3812
+ // src/chat/utils/prompt-strategy.ts
3813
+ var DEFAULT_PROMPT_STRATEGY = {
3814
+ resolve: (config) => {
3815
+ if (config.systemPrompt && config.systemPrompt.length > 0) {
3816
+ return config.systemPrompt;
3817
+ }
3818
+ return void 0;
3819
+ }
3820
+ };
3821
+
3822
+ // src/chat/services/chat.service.ts
3823
+ var DEFAULT_CONFIG = {
3824
+ temperature: 0.8,
3825
+ maxTokens: 500
3826
+ };
3827
+ var ChatService = class {
3828
+ constructor(engine) {
3829
+ this.engine = engine;
3830
+ }
3831
+ engine;
3832
+ config = DEFAULT_CONFIG;
3833
+ promptStrategy = DEFAULT_PROMPT_STRATEGY;
3834
+ storage = null;
3835
+ initialize(config) {
3836
+ this.config = { ...DEFAULT_CONFIG, ...config };
3837
+ }
3838
+ updateConfig(config) {
3839
+ this.config = { ...this.config, ...config };
3840
+ }
3841
+ setPromptStrategy(strategy) {
3842
+ this.promptStrategy = strategy;
3843
+ }
3844
+ setStorage(storage) {
3845
+ this.storage = storage;
3846
+ }
3847
+ async getConversation(conversationId) {
3848
+ const storage = this.requireStorage();
3849
+ const messages = await storage.getMessages(conversationId);
3850
+ if (messages.length === 0) return null;
3851
+ const last = messages[messages.length - 1];
3852
+ const first = messages[0];
3853
+ const companionId = String(first.metadata?.["companionId"] ?? "unknown");
3854
+ const companionName = String(first.metadata?.["companionName"] ?? "Unknown");
3855
+ return {
3856
+ id: conversationId,
3857
+ companionId,
3858
+ companionName,
3859
+ lastMessage: last,
3860
+ messageCount: messages.length,
3861
+ updatedAt: last.timestamp
3862
+ };
3863
+ }
3864
+ async getConversations() {
3865
+ if (this.storage && "getConversations" in this.storage && typeof this.storage.getConversations === "function") {
3866
+ return this.storage.getConversations();
3867
+ }
3868
+ return [];
3869
+ }
3870
+ async sendMessage(input, signal) {
3871
+ const userMessage = {
3872
+ ...messageFormatter.toChatMessage(input.text, "user"),
3873
+ type: input.type ?? "text",
3874
+ ...input.imageUrl !== void 0 ? { imageUrl: input.imageUrl } : {}
3875
+ };
3876
+ const aiResponse = await this.generateAIResponse(
3877
+ input.companionId,
3878
+ input.text,
3879
+ [],
3880
+ signal
3881
+ );
3882
+ return { userMessage, aiResponse };
3883
+ }
3884
+ async generateAIResponse(companionId, userMessage, context = [], signal) {
3885
+ const result = await this.run({
3886
+ ...this.buildRequest(userMessage, context, signal)
3887
+ });
3888
+ return {
3889
+ ...messageFormatter.toChatMessage(result.text, "assistant"),
3890
+ metadata: {
3891
+ companionId,
3892
+ model: result.model,
3893
+ usage: {
3894
+ input_tokens: result.usage?.inputTokens,
3895
+ output_tokens: result.usage?.outputTokens,
3896
+ total_tokens: result.usage?.totalTokens
3897
+ },
3898
+ finishReason: result.finishReason
3899
+ }
3900
+ };
3901
+ }
3902
+ async generateStreamingResponse(userMessage, context, onChunk, signal) {
3903
+ const request = this.buildRequest(userMessage, context, signal);
3904
+ const result = await this.runStream(request, { onChunk });
3905
+ return result.text;
3906
+ }
3907
+ buildRequest(userMessage, context, signal) {
3908
+ const messages = messageFormatter.toCoreMessages(context);
3909
+ messages.push({ role: "user", content: userMessage });
3910
+ return {
3911
+ messages,
3912
+ systemPrompt: this.resolveSystemPrompt(),
3913
+ model: this.config.model,
3914
+ tier: this.config.tier,
3915
+ temperature: this.config.temperature,
3916
+ maxTokens: this.config.maxTokens,
3917
+ ...signal !== void 0 ? { signal } : {}
3918
+ };
3919
+ }
3920
+ resolveSystemPrompt() {
3921
+ const prompt = this.promptStrategy.resolve(this.config);
3922
+ return prompt && prompt.length > 0 ? prompt : void 0;
3923
+ }
3924
+ async run(request) {
3925
+ if (this.engine instanceof ProviderFactory) {
3926
+ return this.engine.chat(request);
3927
+ }
3928
+ return this.engine.generate(request);
3929
+ }
3930
+ async runStream(request, callbacks) {
3931
+ if (this.engine instanceof ProviderFactory) {
3932
+ return this.engine.chatStream(request, callbacks);
3933
+ }
3934
+ return this.engine.stream(request, callbacks);
3935
+ }
3936
+ requireStorage() {
3937
+ if (!this.storage) {
3938
+ throw new AIError(
3939
+ "MISSING_CONFIG" /* MISSING_CONFIG */,
3940
+ "ChatService.getConversation requires setStorage() to be called first."
3941
+ );
3942
+ }
3943
+ return this.storage;
3944
+ }
3945
+ };
3946
+
3947
+ // src/providers/groq/groq-container.ts
3948
+ var container = null;
3949
+ function configureProvider(config) {
3950
+ const configStore = new GroqConfigStore();
3951
+ const transport = new GroqHttpTransport(configStore);
3952
+ const httpClient = new GroqHttpClient({ configStore, transport });
3953
+ httpClient.initialize(config);
3954
+ const textGeneration = new TextGenerationService(
3955
+ httpClient,
3956
+ new CacheManager(),
3957
+ config.textModel
3958
+ );
3959
+ const chat = new ChatService(textGeneration);
3960
+ container = { groqHttpClient: httpClient, textGeneration, chat };
3961
+ return container;
3962
+ }
3963
+ function getProvider() {
3964
+ return container;
3965
+ }
3966
+ function resetProvider() {
3967
+ container?.groqHttpClient.reset();
3968
+ container = null;
3969
+ }
3970
+
3971
+ export { ANTHROPIC_API_VERSION, ANTHROPIC_DEFAULT_MAX_TOKENS, ANTHROPIC_MODELS, ANTHROPIC_TIERS, API_ENDPOINTS, AnthropicHttpTransport, AnthropicProvider, AnthropicRequestBuilder, AuthenticationError, BaseProvider, CancellationError, DEFAULT_BASE_URL, DEFAULT_GENERATION_CONFIG, DEFAULT_MODELS, FalProvider, GEMINI_TIERS, GROQ_MODELS, GROQ_TIERS, GeminiProvider, GenerationFailedError, GroqChatRequestBuilder, GroqConfigStore, GroqHttpClient, GroqHttpTransport, GroqProvider, ProviderError, ProviderFactory, PrunaProvider, TIMEOUTS, TextGenerationService, TimeoutError, ValidationError, configureProvider, consumeAnthropicStream, consumeGroqStream, createAI, createAssistantMessage, createFalProvider, createGeminiProvider, createMessage, createPrunaProvider, createSystemMessage, createUserMessage, defaultEffortForModel, extractContentFromSsePayload, extractTextFromMessages, formatMessagesForDisplay, generateImageThenVideo, generate as generatePruna, generateWithPruna, getProvider, isMediaProvider, isTextProvider, mapAnthropicStopReason, pollForResult, promptToMessages, requestStructuredJson, resetProvider, submitPrediction, toGroqMessages, uploadImage };
2532
3972
  //# sourceMappingURL=index.js.map
2533
3973
  //# sourceMappingURL=index.js.map