@umituz/web-ai 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/{AIErrors-B2ogd1PB.d.ts → AIErrors-CLjXRty-.d.ts} +99 -92
  2. package/dist/{AIErrors-C5EasV_c.d.cts → AIErrors-DjZXUiLT.d.cts} +99 -92
  3. package/dist/PromptBuilder-B5Lm1znm.d.ts +215 -0
  4. package/dist/PromptBuilder-DIUZlfV6.d.cts +215 -0
  5. package/dist/{text-generation.service-BQO3Tvz2.d.cts → cancellation-error-Cj7fJyCg.d.cts} +66 -3
  6. package/dist/{text-generation.service-CcYQM6Ch.d.ts → cancellation-error-CkMn5Kft.d.ts} +66 -3
  7. package/dist/chat/index.cjs.map +1 -1
  8. package/dist/chat/index.d.cts +3 -3
  9. package/dist/chat/index.d.ts +3 -3
  10. package/dist/chat/index.js.map +1 -1
  11. package/dist/{chat.service-CL1qtYxe.d.cts → chat.service-9M-KpLo-.d.cts} +1 -1
  12. package/dist/{chat.service-q05GnhEm.d.ts → chat.service-B4FLFxR1.d.ts} +1 -1
  13. package/dist/content/index.cjs +1582 -452
  14. package/dist/content/index.cjs.map +1 -1
  15. package/dist/content/index.d.cts +165 -219
  16. package/dist/content/index.d.ts +165 -219
  17. package/dist/content/index.js +1525 -453
  18. package/dist/content/index.js.map +1 -1
  19. package/dist/{create-ai-9PW752z8.d.ts → create-ai-0eZFFqTw.d.ts} +1 -1
  20. package/dist/{create-ai-CXYKPK6G.d.cts → create-ai-BrKR_5fQ.d.cts} +1 -1
  21. package/dist/hooks/index.cjs +924 -31
  22. package/dist/hooks/index.cjs.map +1 -1
  23. package/dist/hooks/index.d.cts +44 -8
  24. package/dist/hooks/index.d.ts +44 -8
  25. package/dist/hooks/index.js +919 -32
  26. package/dist/hooks/index.js.map +1 -1
  27. package/dist/index-BUVULo3h.d.ts +177 -0
  28. package/dist/index-BulSkB2L.d.cts +177 -0
  29. package/dist/index.cjs +3057 -563
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +8 -5
  32. package/dist/index.d.ts +8 -5
  33. package/dist/index.js +3011 -564
  34. package/dist/index.js.map +1 -1
  35. package/dist/{provider.factory-DfDfk0dK.d.ts → provider.factory-BwPnqSEU.d.ts} +3 -1
  36. package/dist/{provider.factory-BxsfK4WW.d.cts → provider.factory-DyfJtR8M.d.cts} +3 -1
  37. package/dist/providers/index.cjs +818 -27
  38. package/dist/providers/index.cjs.map +1 -1
  39. package/dist/providers/index.d.cts +50 -8
  40. package/dist/providers/index.d.ts +50 -8
  41. package/dist/providers/index.js +806 -28
  42. package/dist/providers/index.js.map +1 -1
  43. package/dist/wizard/index.d.cts +2 -86
  44. package/dist/wizard/index.d.ts +2 -86
  45. package/package.json +21 -1
  46. package/dist/ContentGeneration-B6iK2B33.d.cts +0 -93
  47. package/dist/ContentGeneration-B6iK2B33.d.ts +0 -93
@@ -515,6 +515,9 @@ var ProviderFactory = class {
515
515
  );
516
516
  }
517
517
  };
518
+ function createProviderFactory(config) {
519
+ return new ProviderFactory(config);
520
+ }
518
521
 
519
522
  // src/core/constants/protocol.constants.ts
520
523
  var SSE_PREFIX = "data: ";
@@ -2093,6 +2096,9 @@ function toGroqMessages(messages, systemPrompt) {
2093
2096
  }
2094
2097
  return wire;
2095
2098
  }
2099
+ function createGroqProvider(config) {
2100
+ return new GroqProvider(config);
2101
+ }
2096
2102
  function findLastIndex2(items, predicate) {
2097
2103
  for (let i = items.length - 1; i >= 0; i--) {
2098
2104
  if (predicate(items[i])) return i;
@@ -2555,39 +2561,811 @@ function createFalProvider(config) {
2555
2561
  return new FalProvider(config);
2556
2562
  }
2557
2563
 
2558
- // src/providers/pruna/pruna-generate.ts
2559
- var prunaCorePromise = null;
2560
- async function loadPrunaCore() {
2561
- if (!prunaCorePromise) {
2562
- const specifier = "@umituz/pruna-provider/core";
2563
- prunaCorePromise = import(specifier);
2564
+ // src/providers/pruna/client/core/constants/limits.ts
2565
+ var DEFAULT_ASPECT_RATIO = "16:9";
2566
+ var P_VIDEO_DEFAULTS = {
2567
+ duration: 5,
2568
+ resolution: "720p",
2569
+ fps: 24,
2570
+ draft: false,
2571
+ promptUpsampling: true
2572
+ };
2573
+ var POLL_DEFAULTS = {
2574
+ intervalMs: 3e3,
2575
+ maxAttempts: 120,
2576
+ maxIntervalMs: 1e4,
2577
+ backoffBase: 1.5,
2578
+ jitterPercent: 0.1
2579
+ };
2580
+ var DEFAULT_DEDUPLICATOR_TTL_MS = 3e4;
2581
+ var DEDUPLICATOR_CLEANUP_GRACE_MS = 100;
2582
+
2583
+ // src/providers/pruna/client/core/constants/network.ts
2584
+ var HTTPS_PREFIX = "https://";
2585
+ var HTTP_PREFIX = "http://";
2586
+ var BASE64_MARKER = "base64,";
2587
+ var PRUNA_BASE_URL = "https://api.pruna.ai";
2588
+ var PRUNA_PREDICTIONS_URL = `${PRUNA_BASE_URL}/v1/predictions`;
2589
+ var PRUNA_FILES_URL = `${PRUNA_BASE_URL}/v1/files`;
2590
+ var AUTH_FAILURE_STATUSES = [401, 403];
2591
+ var MIN_API_KEY_LENGTH = 1;
2592
+ var MAX_API_KEY_LENGTH = 256;
2593
+
2594
+ // src/providers/pruna/client/core/constants/mime-types.ts
2595
+ var MIME_PNG = "image/png";
2596
+ var MIME_JPEG = "image/jpeg";
2597
+ var MIME_WEBP = "image/webp";
2598
+ var DEFAULT_IMAGE_MIME = MIME_PNG;
2599
+ var MIME_FILE_EXTENSION = {
2600
+ [MIME_PNG]: "png",
2601
+ [MIME_JPEG]: "jpg",
2602
+ [MIME_WEBP]: "webp"
2603
+ };
2604
+ var IMAGE_SIGNATURES = {
2605
+ PNG: [137, 80],
2606
+ JPEG: [255, 216],
2607
+ WEBP_RIFF: [82, 73]
2608
+ };
2609
+
2610
+ // src/providers/pruna/client/core/errors/cancellation-error.ts
2611
+ var CANCELLATION_MESSAGE = "Request cancelled by user";
2612
+ var CancellationError = class extends Error {
2613
+ constructor(message = CANCELLATION_MESSAGE) {
2614
+ super(message);
2615
+ this.name = "CancellationError";
2616
+ }
2617
+ };
2618
+
2619
+ // src/providers/pruna/client/core/errors/authentication-error.ts
2620
+ var AUTHENTICATION_MESSAGE = "Authentication failed. Please check your API key.";
2621
+ var AuthenticationError = class extends Error {
2622
+ constructor(message = AUTHENTICATION_MESSAGE) {
2623
+ super(message);
2624
+ this.name = "AuthenticationError";
2625
+ }
2626
+ };
2627
+
2628
+ // src/providers/pruna/client/core/errors/timeout-error.ts
2629
+ var TIMEOUT_MESSAGE = "Generation timed out. Maximum polling attempts reached.";
2630
+ var TimeoutError = class extends Error {
2631
+ constructor(message = TIMEOUT_MESSAGE) {
2632
+ super(message);
2633
+ this.name = "TimeoutError";
2564
2634
  }
2565
- return prunaCorePromise;
2635
+ };
2636
+
2637
+ // src/providers/pruna/client/core/errors/validation-error.ts
2638
+ var ValidationError = class extends Error {
2639
+ field;
2640
+ reason;
2641
+ receivedValue;
2642
+ constructor(context) {
2643
+ super(`Invalid ${context.field}: ${context.reason}`);
2644
+ this.name = "ValidationError";
2645
+ this.field = context.field;
2646
+ this.reason = context.reason;
2647
+ this.receivedValue = context.receivedValue;
2648
+ }
2649
+ };
2650
+
2651
+ // src/providers/pruna/client/core/utils/base64.ts
2652
+ var globalScope = globalThis;
2653
+ var hasBuffer = typeof globalScope.Buffer?.from === "function";
2654
+ function base64ToBytes(raw) {
2655
+ if (hasBuffer) {
2656
+ const bufferFactory = globalScope.Buffer;
2657
+ if (!bufferFactory) {
2658
+ throw new Error("Buffer unavailable despite detection flag");
2659
+ }
2660
+ const buf = bufferFactory.from(raw, "base64");
2661
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
2662
+ }
2663
+ const decoded = atob(raw);
2664
+ return Uint8Array.from(decoded, (char) => char.charCodeAt(0));
2566
2665
  }
2567
- async function* generateWithPruna(request, apiKey, signal, onProgress) {
2568
- const prunaCore = await loadPrunaCore();
2569
- if (prunaCore.generateWithPruna) {
2570
- yield* prunaCore.generateWithPruna(request, apiKey, signal, onProgress);
2571
- return;
2572
- }
2573
- if (!prunaCore.submitPrediction || !prunaCore.pollForResult) {
2574
- throw new Error("@umituz/pruna-provider/core does not export required functions");
2575
- }
2576
- const modelId = request.model || (request.image ? "p-image-edit" : "p-image");
2577
- const prediction = await prunaCore.submitPrediction(modelId, request, apiKey, signal, onProgress);
2578
- if (prediction.output && typeof prediction.output === "object" && "url" in prediction.output) {
2579
- yield {
2580
- stage: "predicting",
2581
- result: { url: prediction.output.url, model: modelId }
2666
+
2667
+ // src/providers/pruna/client/core/utils/mime-detector.ts
2668
+ var MIN_SIGNATURE_BYTES = 2;
2669
+ function matchesSignature(bytes, signature) {
2670
+ if (bytes.length < signature.length) return false;
2671
+ return signature.every((value, index) => bytes[index] === value);
2672
+ }
2673
+ function detectImageMime(bytes) {
2674
+ if (bytes.length < MIN_SIGNATURE_BYTES) return DEFAULT_IMAGE_MIME;
2675
+ if (matchesSignature(bytes, IMAGE_SIGNATURES.PNG)) return MIME_PNG;
2676
+ if (matchesSignature(bytes, IMAGE_SIGNATURES.JPEG)) return MIME_JPEG;
2677
+ if (matchesSignature(bytes, IMAGE_SIGNATURES.WEBP_RIFF)) return MIME_WEBP;
2678
+ return DEFAULT_IMAGE_MIME;
2679
+ }
2680
+
2681
+ // src/providers/pruna/client/core/utils/api-error-parser.ts
2682
+ async function parseApiError(response, fallback) {
2683
+ const payload = await response.json().catch(() => ({ message: response.statusText }));
2684
+ const message = payload.message ?? `${fallback}: ${response.status}`;
2685
+ const error = new Error(message);
2686
+ error.statusCode = response.status;
2687
+ return error;
2688
+ }
2689
+
2690
+ // src/providers/pruna/client/core/protocol/http-status.ts
2691
+ function isAuthFailureStatus(status) {
2692
+ return AUTH_FAILURE_STATUSES.includes(status);
2693
+ }
2694
+
2695
+ // src/providers/pruna/client/core/protocol/job-status.ts
2696
+ var TERMINAL_SUCCESS_STATUSES = ["succeeded", "completed"];
2697
+ var TERMINAL_FAILURE_STATUSES = ["failed"];
2698
+ function isTerminalSuccessStatus(status) {
2699
+ if (!status) return false;
2700
+ return TERMINAL_SUCCESS_STATUSES.includes(status);
2701
+ }
2702
+ function isTerminalFailureStatus(status) {
2703
+ if (!status) return false;
2704
+ return TERMINAL_FAILURE_STATUSES.includes(status);
2705
+ }
2706
+
2707
+ // src/providers/pruna/client/core/protocol/abort-signal-classifier.ts
2708
+ function isAbortError(err) {
2709
+ if (err instanceof DOMException && err.code === DOMException.ABORT_ERR) return true;
2710
+ if (err instanceof Error && err.name === "AbortError") return true;
2711
+ return false;
2712
+ }
2713
+
2714
+ // src/providers/pruna/client/core/protocol/pruna-url-builder.ts
2715
+ function isAbsoluteHttpUrl(value) {
2716
+ return value.startsWith(HTTPS_PREFIX) || value.startsWith(HTTP_PREFIX);
2717
+ }
2718
+ function isHttpsUrl(value) {
2719
+ return value.startsWith(HTTPS_PREFIX);
2720
+ }
2721
+ function buildPrunaApiUrl(pathOrUrl) {
2722
+ if (isAbsoluteHttpUrl(pathOrUrl)) return pathOrUrl;
2723
+ return `${PRUNA_BASE_URL}${pathOrUrl}`;
2724
+ }
2725
+ function buildFileUrlById(fileId) {
2726
+ return `${PRUNA_FILES_URL}/${fileId}`;
2727
+ }
2728
+
2729
+ // src/providers/pruna/client/core/math/exponential-backoff.ts
2730
+ function calculateExponentialBackoff2(inputs) {
2731
+ const {
2732
+ attempt,
2733
+ baseIntervalMs,
2734
+ maxIntervalMs = POLL_DEFAULTS.maxIntervalMs,
2735
+ jitterPercent = POLL_DEFAULTS.jitterPercent,
2736
+ growthBase = POLL_DEFAULTS.backoffBase
2737
+ } = inputs;
2738
+ const safeAttempt = Math.max(0, attempt);
2739
+ const safeBase = Math.max(0, baseIntervalMs);
2740
+ const exponentialDelay = Math.min(safeBase * Math.pow(growthBase, safeAttempt), maxIntervalMs);
2741
+ const jitter = exponentialDelay * jitterPercent * Math.random();
2742
+ return exponentialDelay + jitter;
2743
+ }
2744
+
2745
+ // src/providers/pruna/client/core/utils/uri-resolver.ts
2746
+ var uriCache = /* @__PURE__ */ new WeakMap();
2747
+ function stripBase64Prefix(image) {
2748
+ if (isHttpsUrl(image)) return image;
2749
+ const base64Index = image.indexOf(BASE64_MARKER);
2750
+ return base64Index !== -1 ? image.slice(base64Index + BASE64_MARKER.length) : image;
2751
+ }
2752
+ function readOutput(output) {
2753
+ if (typeof output === "string") return output;
2754
+ if (Array.isArray(output) && output.length > 0) return output[0] ?? null;
2755
+ if (output !== null && typeof output === "object" && "url" in output) {
2756
+ return output.url;
2757
+ }
2758
+ return null;
2759
+ }
2760
+ function extractUri(data) {
2761
+ const cached = uriCache.get(data);
2762
+ if (cached !== void 0) return cached;
2763
+ if (!isTerminalSuccessStatus(data.status)) {
2764
+ uriCache.set(data, null);
2765
+ return null;
2766
+ }
2767
+ const result = data.generation_url ?? readOutput(data.output) ?? data.video_url ?? data.data ?? null;
2768
+ uriCache.set(data, result);
2769
+ return result;
2770
+ }
2771
+ function resolveUri(uri) {
2772
+ return uri.startsWith("/") ? `${PRUNA_BASE_URL}${uri}` : uri;
2773
+ }
2774
+
2775
+ // src/providers/pruna/client/core/services/pruna-client.service.ts
2776
+ function buildFileExtension(mime) {
2777
+ return MIME_FILE_EXTENSION[mime] ?? "bin";
2778
+ }
2779
+ async function uploadImage(base64OrUrl, apiKey, onProgress) {
2780
+ if (isHttpsUrl(base64OrUrl)) return base64OrUrl;
2781
+ if (base64OrUrl.startsWith(HTTP_PREFIX)) {
2782
+ throw new Error("Only HTTPS URLs are supported for security.");
2783
+ }
2784
+ onProgress?.("uploading");
2785
+ const raw = stripBase64Prefix(base64OrUrl);
2786
+ let bytes;
2787
+ try {
2788
+ bytes = base64ToBytes(raw);
2789
+ } catch {
2790
+ throw new Error("Invalid image format. Provide base64 or a valid HTTPS URL.");
2791
+ }
2792
+ const mime = detectImageMime(bytes);
2793
+ const buffer = bytes.buffer.slice(
2794
+ bytes.byteOffset,
2795
+ bytes.byteOffset + bytes.byteLength
2796
+ );
2797
+ const blob = new Blob([buffer], { type: mime });
2798
+ const formData = new FormData();
2799
+ formData.append("content", blob, `upload.${buildFileExtension(mime)}`);
2800
+ const res = await fetch(PRUNA_FILES_URL, {
2801
+ method: "POST",
2802
+ headers: { apikey: apiKey },
2803
+ body: formData
2804
+ });
2805
+ if (!res.ok) {
2806
+ throw await parseApiError(res, "File upload error");
2807
+ }
2808
+ const data = await res.json();
2809
+ return data.urls?.get ?? buildFileUrlById(data.id ?? "");
2810
+ }
2811
+ async function submitPrediction(model, input, apiKey, signal, onProgress) {
2812
+ onProgress?.("predicting");
2813
+ const res = await fetch(PRUNA_PREDICTIONS_URL, {
2814
+ method: "POST",
2815
+ headers: {
2816
+ apikey: apiKey,
2817
+ Model: model,
2818
+ "Try-Sync": "true",
2819
+ "Content-Type": "application/json"
2820
+ },
2821
+ body: JSON.stringify({ input }),
2822
+ signal
2823
+ });
2824
+ if (!res.ok) {
2825
+ throw await parseApiError(res, "API error");
2826
+ }
2827
+ return res.json();
2828
+ }
2829
+ var GenerationFailedError = class extends Error {
2830
+ constructor(message) {
2831
+ super(message);
2832
+ this.name = "GenerationFailedError";
2833
+ }
2834
+ };
2835
+ async function attemptPoll(fullUrl, apiKey, signal) {
2836
+ let res;
2837
+ try {
2838
+ res = await fetch(fullUrl, { headers: { apikey: apiKey }, signal });
2839
+ } catch (err) {
2840
+ if (isAbortError(err)) throw new CancellationError();
2841
+ return { outcome: "transient-failure" };
2842
+ }
2843
+ if (!res.ok) {
2844
+ if (isAuthFailureStatus(res.status)) throw new AuthenticationError();
2845
+ return { outcome: "transient-failure" };
2846
+ }
2847
+ let data;
2848
+ try {
2849
+ data = await res.json();
2850
+ } catch {
2851
+ return { outcome: "transient-failure" };
2852
+ }
2853
+ if (isTerminalSuccessStatus(data.status)) {
2854
+ const uri = extractUri(data);
2855
+ if (uri) return { outcome: "succeeded", uri: resolveUri(uri) };
2856
+ return { outcome: "transient-failure" };
2857
+ }
2858
+ if (isTerminalFailureStatus(data.status)) {
2859
+ throw new GenerationFailedError(data.error ?? "Generation failed during processing.");
2860
+ }
2861
+ return { outcome: "continue" };
2862
+ }
2863
+ async function pollForResult(pollUrl, apiKey, maxAttempts = POLL_DEFAULTS.maxAttempts, intervalMs = POLL_DEFAULTS.intervalMs, signal, onProgress) {
2864
+ const fullUrl = isAbsoluteHttpUrl(pollUrl) ? pollUrl : buildPrunaApiUrl(pollUrl);
2865
+ const controller = new AbortController();
2866
+ const onExternalAbort = () => controller.abort();
2867
+ signal?.addEventListener("abort", onExternalAbort);
2868
+ try {
2869
+ for (let i = 0; i < maxAttempts; i++) {
2870
+ if (signal?.aborted) throw new CancellationError();
2871
+ const delay = calculateExponentialBackoff2({ attempt: i, baseIntervalMs: intervalMs });
2872
+ await new Promise((resolve) => setTimeout(resolve, delay));
2873
+ if (signal?.aborted) throw new CancellationError();
2874
+ onProgress?.("polling", i + 1);
2875
+ const result = await attemptPoll(fullUrl, apiKey, controller.signal);
2876
+ if (result.outcome === "succeeded" && result.uri) {
2877
+ return result.uri;
2878
+ }
2879
+ }
2880
+ throw new TimeoutError();
2881
+ } finally {
2882
+ signal?.removeEventListener("abort", onExternalAbort);
2883
+ controller.abort();
2884
+ }
2885
+ }
2886
+
2887
+ // src/providers/pruna/client/core/math/comparison-hash.ts
2888
+ function sortKeysDeep(value) {
2889
+ if (Array.isArray(value)) {
2890
+ return value.map(sortKeysDeep);
2891
+ }
2892
+ if (value !== null && typeof value === "object") {
2893
+ const record = value;
2894
+ return Object.keys(record).sort().reduce((acc, key) => {
2895
+ acc[key] = sortKeysDeep(record[key]);
2896
+ return acc;
2897
+ }, {});
2898
+ }
2899
+ return value;
2900
+ }
2901
+ function hashValue(value) {
2902
+ if (value === null || typeof value !== "object") {
2903
+ return String(value);
2904
+ }
2905
+ try {
2906
+ return JSON.stringify(sortKeysDeep(value));
2907
+ } catch {
2908
+ return String(value);
2909
+ }
2910
+ }
2911
+ function compositeHash(...values) {
2912
+ return values.map(hashValue).join("|");
2913
+ }
2914
+
2915
+ // src/providers/pruna/client/core/math/cache-key-derivation.ts
2916
+ function deriveGenerationCacheKey(apiKey, input) {
2917
+ return `${apiKey}:${compositeHash(input)}`;
2918
+ }
2919
+
2920
+ // src/providers/pruna/client/core/validation/api-key-validator.ts
2921
+ function assertValidApiKey(apiKey) {
2922
+ if (typeof apiKey !== "string") {
2923
+ throw new ValidationError({
2924
+ field: "apiKey",
2925
+ reason: "must be a string",
2926
+ receivedValue: typeof apiKey
2927
+ });
2928
+ }
2929
+ if (apiKey.length < MIN_API_KEY_LENGTH) {
2930
+ throw new ValidationError({
2931
+ field: "apiKey",
2932
+ reason: "must not be empty",
2933
+ receivedValue: apiKey
2934
+ });
2935
+ }
2936
+ if (apiKey.length > MAX_API_KEY_LENGTH) {
2937
+ throw new ValidationError({
2938
+ field: "apiKey",
2939
+ reason: `exceeds maximum length of ${MAX_API_KEY_LENGTH}`,
2940
+ receivedValue: apiKey.length
2941
+ });
2942
+ }
2943
+ }
2944
+
2945
+ // src/providers/pruna/client/core/validation/image-source-validator.ts
2946
+ function classifyImageSource(source) {
2947
+ if (source.startsWith(HTTPS_PREFIX)) return "https-url";
2948
+ if (source.startsWith(HTTP_PREFIX)) return "http-url";
2949
+ if (source.includes(BASE64_MARKER) || /^[A-Za-z0-9+/=]+$/.test(source)) return "base64";
2950
+ return "unknown";
2951
+ }
2952
+ function assertSecureImageSource(image, fieldName = "image") {
2953
+ if (typeof image !== "string") {
2954
+ throw new ValidationError({
2955
+ field: fieldName,
2956
+ reason: "must be a string",
2957
+ receivedValue: typeof image
2958
+ });
2959
+ }
2960
+ const kind = classifyImageSource(image);
2961
+ if (kind === "http-url") {
2962
+ throw new ValidationError({
2963
+ field: fieldName,
2964
+ reason: "HTTP URLs are not supported; use HTTPS or a base64 data URI",
2965
+ receivedValue: image
2966
+ });
2967
+ }
2968
+ if (kind === "unknown") {
2969
+ throw new ValidationError({
2970
+ field: fieldName,
2971
+ reason: "must be an HTTPS URL or a base64 data URI",
2972
+ receivedValue: typeof image
2973
+ });
2974
+ }
2975
+ }
2976
+
2977
+ // src/providers/pruna/client/core/validation/aspect-ratio-validator.ts
2978
+ var SUPPORTED_ASPECT_RATIOS = [
2979
+ "16:9",
2980
+ "9:16",
2981
+ "1:1",
2982
+ "4:3",
2983
+ "3:4",
2984
+ "3:2",
2985
+ "2:3"
2986
+ ];
2987
+ function isSupportedAspectRatio(value) {
2988
+ return typeof value === "string" && SUPPORTED_ASPECT_RATIOS.includes(value);
2989
+ }
2990
+ function assertValidAspectRatio(value, fieldName = "aspect_ratio") {
2991
+ if (value === void 0 || value === null) return;
2992
+ if (!isSupportedAspectRatio(value)) {
2993
+ throw new ValidationError({
2994
+ field: fieldName,
2995
+ reason: `must be one of ${SUPPORTED_ASPECT_RATIOS.join(", ")}`,
2996
+ receivedValue: value
2997
+ });
2998
+ }
2999
+ }
3000
+
3001
+ // src/providers/pruna/client/core/validation/resolution-validator.ts
3002
+ var SUPPORTED_RESOLUTIONS = ["720p", "1080p"];
3003
+ function isSupportedResolution(value) {
3004
+ return typeof value === "string" && SUPPORTED_RESOLUTIONS.includes(value);
3005
+ }
3006
+ function assertValidResolution(value, fieldName = "resolution") {
3007
+ if (value === void 0 || value === null) return;
3008
+ if (!isSupportedResolution(value)) {
3009
+ throw new ValidationError({
3010
+ field: fieldName,
3011
+ reason: `must be one of ${SUPPORTED_RESOLUTIONS.join(", ")}`,
3012
+ receivedValue: value
3013
+ });
3014
+ }
3015
+ }
3016
+
3017
+ // src/providers/pruna/client/core/validation/duration-validator.ts
3018
+ var MIN_VIDEO_DURATION_SECONDS = 1;
3019
+ var MAX_VIDEO_DURATION_SECONDS = 30;
3020
+ function isValidDuration(value) {
3021
+ return typeof value === "number" && Number.isFinite(value) && value >= MIN_VIDEO_DURATION_SECONDS && value <= MAX_VIDEO_DURATION_SECONDS && Number.isInteger(value);
3022
+ }
3023
+ function assertValidDuration(value, fieldName = "duration") {
3024
+ if (value === void 0 || value === null) return;
3025
+ if (!isValidDuration(value)) {
3026
+ throw new ValidationError({
3027
+ field: fieldName,
3028
+ reason: `must be an integer between ${MIN_VIDEO_DURATION_SECONDS} and ${MAX_VIDEO_DURATION_SECONDS} seconds`,
3029
+ receivedValue: value
3030
+ });
3031
+ }
3032
+ }
3033
+
3034
+ // src/providers/pruna/client/core/validation/prompt-validator.ts
3035
+ var MIN_PROMPT_LENGTH = 1;
3036
+ var MAX_PROMPT_LENGTH = 4e3;
3037
+ function assertValidPrompt(value, fieldName = "prompt") {
3038
+ if (typeof value !== "string") {
3039
+ throw new ValidationError({
3040
+ field: fieldName,
3041
+ reason: "must be a string",
3042
+ receivedValue: typeof value
3043
+ });
3044
+ }
3045
+ if (value.trim().length < MIN_PROMPT_LENGTH) {
3046
+ throw new ValidationError({
3047
+ field: fieldName,
3048
+ reason: "must not be empty or whitespace-only",
3049
+ receivedValue: value
3050
+ });
3051
+ }
3052
+ if (value.length > MAX_PROMPT_LENGTH) {
3053
+ throw new ValidationError({
3054
+ field: fieldName,
3055
+ reason: `exceeds maximum length of ${MAX_PROMPT_LENGTH} characters`,
3056
+ receivedValue: value.length
3057
+ });
3058
+ }
3059
+ }
3060
+
3061
+ // src/providers/pruna/client/core/validation/seed-validator.ts
3062
+ var MIN_SEED = -2147483648;
3063
+ var MAX_SEED = 2147483647;
3064
+ function isValidSeed(value) {
3065
+ return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= MIN_SEED && value <= MAX_SEED;
3066
+ }
3067
+ function assertValidSeed(value, fieldName = "seed") {
3068
+ if (value === void 0 || value === null) return;
3069
+ if (!isValidSeed(value)) {
3070
+ throw new ValidationError({
3071
+ field: fieldName,
3072
+ reason: `must be an integer in [${MIN_SEED}, ${MAX_SEED}]`,
3073
+ receivedValue: value
3074
+ });
3075
+ }
3076
+ }
3077
+
3078
+ // src/providers/pruna/client/core/protocol/input-type-guard.ts
3079
+ function isTextToImageInput(input) {
3080
+ return input.model === "p-image";
3081
+ }
3082
+ function isImageToImageInput(input) {
3083
+ return input.model === "p-image-edit";
3084
+ }
3085
+ function isImageToVideoInput(input) {
3086
+ return input.model === "p-video";
3087
+ }
3088
+
3089
+ // src/providers/pruna/client/core/utils/request-deduplicator.ts
3090
+ var RequestDeduplicator2 = class {
3091
+ pendingRequests = /* @__PURE__ */ new Map();
3092
+ ttl;
3093
+ constructor(ttl = DEFAULT_DEDUPLICATOR_TTL_MS) {
3094
+ this.ttl = ttl;
3095
+ }
3096
+ execute(key, fn) {
3097
+ const existing = this.pendingRequests.get(key);
3098
+ if (existing) {
3099
+ if (Date.now() - existing.timestamp < this.ttl) {
3100
+ return existing.promise;
3101
+ }
3102
+ this.pendingRequests.delete(key);
3103
+ }
3104
+ const promise = fn().finally(() => {
3105
+ setTimeout(() => {
3106
+ this.pendingRequests.delete(key);
3107
+ }, DEDUPLICATOR_CLEANUP_GRACE_MS);
3108
+ });
3109
+ this.pendingRequests.set(key, { promise, timestamp: Date.now() });
3110
+ return promise;
3111
+ }
3112
+ clear() {
3113
+ this.pendingRequests.clear();
3114
+ }
3115
+ size() {
3116
+ this.purgeExpired();
3117
+ return this.pendingRequests.size;
3118
+ }
3119
+ purgeExpired() {
3120
+ const now = Date.now();
3121
+ for (const [key, value] of this.pendingRequests) {
3122
+ if (now - value.timestamp >= this.ttl) {
3123
+ this.pendingRequests.delete(key);
3124
+ }
3125
+ }
3126
+ }
3127
+ };
3128
+ var globalDeduplicator = new RequestDeduplicator2();
3129
+
3130
+ // src/providers/pruna/client/generation/builders/text-to-image.builder.ts
3131
+ var TextToImageInputBuilder = class {
3132
+ model = "p-image";
3133
+ build(input, _context) {
3134
+ const payload = {
3135
+ prompt: input.prompt,
3136
+ aspect_ratio: input.aspect_ratio ?? DEFAULT_ASPECT_RATIO
2582
3137
  };
2583
- return;
3138
+ if (input.seed !== void 0) {
3139
+ payload.seed = input.seed;
3140
+ }
3141
+ return Promise.resolve(payload);
3142
+ }
3143
+ };
3144
+
3145
+ // src/providers/pruna/client/generation/builders/image-to-image.builder.ts
3146
+ var ImageToImageInputBuilder = class {
3147
+ model = "p-image-edit";
3148
+ build(input, _context) {
3149
+ const payload = {
3150
+ images: [stripBase64Prefix(input.image)],
3151
+ prompt: input.prompt,
3152
+ aspect_ratio: input.aspect_ratio ?? DEFAULT_ASPECT_RATIO
3153
+ };
3154
+ if (input.seed !== void 0) {
3155
+ payload.seed = input.seed;
3156
+ }
3157
+ return Promise.resolve(payload);
3158
+ }
3159
+ };
3160
+
3161
+ // src/providers/pruna/client/generation/builders/image-to-video.builder.ts
3162
+ var ImageToVideoInputBuilder = class {
3163
+ model = "p-video";
3164
+ async build(input, context) {
3165
+ if (context.signal?.aborted) {
3166
+ throw new CancellationError();
3167
+ }
3168
+ const fileUrl = await uploadImage(
3169
+ input.image,
3170
+ context.apiKey,
3171
+ context.onProgress
3172
+ );
3173
+ return {
3174
+ image: fileUrl,
3175
+ prompt: input.prompt,
3176
+ duration: input.duration ?? P_VIDEO_DEFAULTS.duration,
3177
+ resolution: input.resolution ?? P_VIDEO_DEFAULTS.resolution,
3178
+ fps: P_VIDEO_DEFAULTS.fps,
3179
+ draft: input.draft ?? P_VIDEO_DEFAULTS.draft,
3180
+ aspect_ratio: input.aspect_ratio ?? DEFAULT_ASPECT_RATIO,
3181
+ prompt_upsampling: P_VIDEO_DEFAULTS.promptUpsampling
3182
+ };
3183
+ }
3184
+ };
3185
+
3186
+ // src/providers/pruna/client/generation/builders/model-input-builder.ts
3187
+ var ModelInputBuilderRegistry = class {
3188
+ builders = /* @__PURE__ */ new Map();
3189
+ register(builder) {
3190
+ this.builders.set(builder.model, builder);
3191
+ return this;
3192
+ }
3193
+ resolve(model) {
3194
+ const builder = this.builders.get(model);
3195
+ if (!builder) {
3196
+ throw new Error(`No input builder registered for model: ${model}`);
3197
+ }
3198
+ return builder;
3199
+ }
3200
+ };
3201
+
3202
+ // src/providers/pruna/client/generation/builders/builder-factory.ts
3203
+ function createDefaultBuilderRegistry() {
3204
+ return new ModelInputBuilderRegistry().register(new TextToImageInputBuilder()).register(new ImageToImageInputBuilder()).register(new ImageToVideoInputBuilder());
3205
+ }
3206
+
3207
+ // src/providers/pruna/client/generation/services/generation.service.ts
3208
+ var builderRegistry = createDefaultBuilderRegistry();
3209
+ function validateGenerateInput(input) {
3210
+ assertValidPrompt(input.prompt, `${input.model}.prompt`);
3211
+ assertValidAspectRatio(input.aspect_ratio, `${input.model}.aspect_ratio`);
3212
+ if (isTextToImageInput(input)) {
3213
+ assertValidSeed(input.seed, `${input.model}.seed`);
3214
+ } else if (isImageToImageInput(input)) {
3215
+ assertSecureImageSource(input.image, `${input.model}.image`);
3216
+ assertValidSeed(input.seed, `${input.model}.seed`);
3217
+ } else if (isImageToVideoInput(input)) {
3218
+ assertSecureImageSource(input.image, `${input.model}.image`);
3219
+ assertValidDuration(input.duration, `${input.model}.duration`);
3220
+ assertValidResolution(input.resolution, `${input.model}.resolution`);
2584
3221
  }
2585
- const pollUrl = prediction.status_url || prediction.generation_url;
3222
+ }
3223
+ async function generate(apiKey, input, options = {}) {
3224
+ assertValidApiKey(apiKey);
3225
+ validateGenerateInput(input);
3226
+ const { signal, onProgress, useCache = true } = options;
3227
+ if (useCache) {
3228
+ const cacheKey = deriveGenerationCacheKey(apiKey, input);
3229
+ return globalDeduplicator.execute(
3230
+ cacheKey,
3231
+ () => generateInternal(apiKey, input, { signal, onProgress })
3232
+ );
3233
+ }
3234
+ return generateInternal(apiKey, input, { signal, onProgress });
3235
+ }
3236
+ async function generateInternal(apiKey, input, options) {
3237
+ const { signal, onProgress } = options;
3238
+ const builder = builderRegistry.resolve(input.model);
3239
+ const modelInput = await builder.build(input, { apiKey, signal, onProgress });
3240
+ const response = await submitPrediction(input.model, modelInput, apiKey, signal, onProgress);
3241
+ const syncUri = extractUri(response);
3242
+ if (syncUri) {
3243
+ return { url: resolveUri(syncUri), model: input.model };
3244
+ }
3245
+ const pollUrl = response.get_url ?? response.status_url;
2586
3246
  if (!pollUrl) {
2587
- throw new Error("Pruna prediction response missing polling URL");
3247
+ throw new Error("Pruna API returned no result and no polling URL.");
3248
+ }
3249
+ const url = await pollForResult(
3250
+ pollUrl,
3251
+ apiKey,
3252
+ POLL_DEFAULTS.maxAttempts,
3253
+ POLL_DEFAULTS.intervalMs,
3254
+ signal,
3255
+ onProgress
3256
+ );
3257
+ return { url, model: input.model };
3258
+ }
3259
+ async function generateImageThenVideo(apiKey, input, options = {}) {
3260
+ assertValidApiKey(apiKey);
3261
+ assertValidPrompt(input.prompt, "text-to-video.prompt");
3262
+ assertValidAspectRatio(input.aspect_ratio, "text-to-video.aspect_ratio");
3263
+ assertValidDuration(input.duration, "text-to-video.duration");
3264
+ assertValidResolution(input.resolution, "text-to-video.resolution");
3265
+ const { signal, onProgress } = options;
3266
+ const aspectRatio = input.aspect_ratio ?? DEFAULT_ASPECT_RATIO;
3267
+ onProgress?.("predicting", 1);
3268
+ const imageResult = await generate(
3269
+ apiKey,
3270
+ { model: "p-image", prompt: input.prompt, aspect_ratio: aspectRatio },
3271
+ { signal }
3272
+ );
3273
+ onProgress?.("predicting", 2);
3274
+ return generate(
3275
+ apiKey,
3276
+ {
3277
+ model: "p-video",
3278
+ prompt: input.prompt,
3279
+ image: imageResult.url,
3280
+ duration: input.duration ?? P_VIDEO_DEFAULTS.duration,
3281
+ resolution: input.resolution ?? P_VIDEO_DEFAULTS.resolution,
3282
+ aspect_ratio: aspectRatio,
3283
+ draft: input.draft ?? P_VIDEO_DEFAULTS.draft
3284
+ },
3285
+ { signal, onProgress }
3286
+ );
3287
+ }
3288
+
3289
+ // src/providers/pruna/pruna-generate.ts
3290
+ async function* generateWithPruna(request, apiKey, signal, onProgress) {
3291
+ const stages = [];
3292
+ let notify = null;
3293
+ let done = false;
3294
+ const wake = () => {
3295
+ const fn = notify;
3296
+ if (fn) {
3297
+ notify = null;
3298
+ fn();
3299
+ }
3300
+ };
3301
+ const handleProgress = (stage) => {
3302
+ stages.push({ stage });
3303
+ onProgress?.(stage);
3304
+ wake();
3305
+ };
3306
+ const promise = (async () => {
3307
+ try {
3308
+ const model = request.model ?? (request.image ? "p-image-edit" : "p-image");
3309
+ if (model === "p-video" && !request.image && request.prompt) {
3310
+ const videoRes = await generateImageThenVideo(
3311
+ apiKey,
3312
+ {
3313
+ prompt: request.prompt,
3314
+ duration: request.duration ?? 5,
3315
+ aspect_ratio: request.aspectRatio ?? request.aspect_ratio ?? "16:9"
3316
+ },
3317
+ { signal, onProgress: handleProgress }
3318
+ );
3319
+ stages.push({
3320
+ stage: "polling",
3321
+ result: { url: videoRes.url, model: "p-video" }
3322
+ });
3323
+ return;
3324
+ }
3325
+ const input = {
3326
+ model,
3327
+ prompt: request.prompt ?? "",
3328
+ aspect_ratio: request.aspectRatio ?? request.aspect_ratio ?? "1:1",
3329
+ ...request.image ? { image: request.image } : {},
3330
+ ...request.duration ? { duration: request.duration } : {},
3331
+ ...request.resolution ? { resolution: request.resolution } : {},
3332
+ ...request.seed !== void 0 ? { seed: request.seed } : {}
3333
+ };
3334
+ const res = await generate(apiKey, input, {
3335
+ signal,
3336
+ onProgress: handleProgress
3337
+ });
3338
+ stages.push({
3339
+ stage: "polling",
3340
+ result: { url: res.url, model: res.model }
3341
+ });
3342
+ } catch (err) {
3343
+ stages.push({
3344
+ stage: "processing",
3345
+ error: err instanceof Error ? err : new Error(String(err))
3346
+ });
3347
+ } finally {
3348
+ done = true;
3349
+ wake();
3350
+ }
3351
+ })();
3352
+ while (!done || stages.length > 0) {
3353
+ if (stages.length === 0 && !done) {
3354
+ await new Promise((resolve) => {
3355
+ notify = resolve;
3356
+ });
3357
+ notify = null;
3358
+ }
3359
+ while (stages.length > 0) {
3360
+ const nextStage = stages.shift();
3361
+ if (!nextStage) continue;
3362
+ yield nextStage;
3363
+ if (nextStage.error) {
3364
+ throw nextStage.error;
3365
+ }
3366
+ }
2588
3367
  }
2589
- const resultUrl = await prunaCore.pollForResult(pollUrl, apiKey, 60, 2e3, signal, onProgress);
2590
- yield { stage: "polling", result: { url: resultUrl, model: modelId } };
3368
+ await promise;
2591
3369
  }
2592
3370
 
2593
3371
  // src/providers/pruna/pruna.provider.ts
@@ -3206,7 +3984,10 @@ exports.API_ENDPOINTS = API_ENDPOINTS;
3206
3984
  exports.AnthropicHttpTransport = AnthropicHttpTransport;
3207
3985
  exports.AnthropicProvider = AnthropicProvider;
3208
3986
  exports.AnthropicRequestBuilder = AnthropicRequestBuilder;
3987
+ exports.AuthenticationError = AuthenticationError;
3988
+ exports.BaseAIProvider = BaseProvider;
3209
3989
  exports.BaseProvider = BaseProvider;
3990
+ exports.CancellationError = CancellationError;
3210
3991
  exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
3211
3992
  exports.DEFAULT_GENERATION_CONFIG = DEFAULT_GENERATION_CONFIG;
3212
3993
  exports.DEFAULT_MODELS = DEFAULT_MODELS;
@@ -3215,6 +3996,7 @@ exports.GEMINI_TIERS = GEMINI_TIERS;
3215
3996
  exports.GROQ_MODELS = GROQ_MODELS;
3216
3997
  exports.GROQ_TIERS = GROQ_TIERS;
3217
3998
  exports.GeminiProvider = GeminiProvider;
3999
+ exports.GenerationFailedError = GenerationFailedError;
3218
4000
  exports.GroqChatRequestBuilder = GroqChatRequestBuilder;
3219
4001
  exports.GroqConfigStore = GroqConfigStore;
3220
4002
  exports.GroqHttpClient = GroqHttpClient;
@@ -3225,6 +4007,8 @@ exports.ProviderFactory = ProviderFactory;
3225
4007
  exports.PrunaProvider = PrunaProvider;
3226
4008
  exports.TIMEOUTS = TIMEOUTS;
3227
4009
  exports.TextGenerationService = TextGenerationService;
4010
+ exports.TimeoutError = TimeoutError;
4011
+ exports.ValidationError = ValidationError;
3228
4012
  exports.configureProvider = configureProvider;
3229
4013
  exports.consumeAnthropicStream = consumeAnthropicStream;
3230
4014
  exports.consumeGroqStream = consumeGroqStream;
@@ -3232,7 +4016,9 @@ exports.createAI = createAI;
3232
4016
  exports.createAssistantMessage = createAssistantMessage;
3233
4017
  exports.createFalProvider = createFalProvider;
3234
4018
  exports.createGeminiProvider = createGeminiProvider;
4019
+ exports.createGroqProvider = createGroqProvider;
3235
4020
  exports.createMessage = createMessage;
4021
+ exports.createProviderFactory = createProviderFactory;
3236
4022
  exports.createPrunaProvider = createPrunaProvider;
3237
4023
  exports.createSystemMessage = createSystemMessage;
3238
4024
  exports.createUserMessage = createUserMessage;
@@ -3240,14 +4026,19 @@ exports.defaultEffortForModel = defaultEffortForModel;
3240
4026
  exports.extractContentFromSsePayload = extractContentFromSsePayload;
3241
4027
  exports.extractTextFromMessages = extractTextFromMessages;
3242
4028
  exports.formatMessagesForDisplay = formatMessagesForDisplay;
4029
+ exports.generateImageThenVideo = generateImageThenVideo;
4030
+ exports.generatePruna = generate;
3243
4031
  exports.generateWithPruna = generateWithPruna;
3244
4032
  exports.getProvider = getProvider;
3245
4033
  exports.isMediaProvider = isMediaProvider;
3246
4034
  exports.isTextProvider = isTextProvider;
3247
4035
  exports.mapAnthropicStopReason = mapAnthropicStopReason;
4036
+ exports.pollForResult = pollForResult;
3248
4037
  exports.promptToMessages = promptToMessages;
3249
4038
  exports.requestStructuredJson = requestStructuredJson;
3250
4039
  exports.resetProvider = resetProvider;
4040
+ exports.submitPrediction = submitPrediction;
3251
4041
  exports.toGroqMessages = toGroqMessages;
4042
+ exports.uploadImage = uploadImage;
3252
4043
  //# sourceMappingURL=index.cjs.map
3253
4044
  //# sourceMappingURL=index.cjs.map