@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.
package/dist/index.js CHANGED
@@ -130,6 +130,14 @@ var AIError = class _AIError extends Error {
130
130
  }
131
131
  };
132
132
 
133
+ // src/core/errors/groq-error.ts
134
+ var GroqError = class extends AIError {
135
+ constructor(type, message, cause, metadata = {}) {
136
+ super(type, message, cause, metadata);
137
+ this.name = "GroqError";
138
+ }
139
+ };
140
+
133
141
  // src/core/errors/error-classifier.ts
134
142
  var DEFAULT_RETRYABLE_TYPES = /* @__PURE__ */ new Set([
135
143
  "NETWORK_ERROR" /* NETWORK_ERROR */,
@@ -408,6 +416,8 @@ function calculateExponentialBackoff(params) {
408
416
  // src/core/constants/identifier.constants.ts
409
417
  var MESSAGE_ID_PREFIX = "msg-";
410
418
  var CHAT_MESSAGE_ID_SUFFIX_LENGTH = 9;
419
+ var CACHE_KEY_TYPE_COMPLETION = "completion";
420
+ var CACHE_KEY_TYPE_CHAT = "chat";
411
421
  var USER_ROLE = "user";
412
422
  var ASSISTANT_ROLE = "assistant";
413
423
  var QUEUE_PRIORITY_USER_REQUEST = 10;
@@ -1879,12 +1889,22 @@ var DEFAULT_GENERATION_CONFIG = {
1879
1889
  maxTokens: 1024,
1880
1890
  topP: 1
1881
1891
  };
1892
+ var DEFAULT_MODELS = {
1893
+ TEXT: GROQ_MODELS.LLAMA_3_1_8B_INSTANT,
1894
+ FAST: GROQ_MODELS.LLAMA_3_1_8B_INSTANT,
1895
+ EXPERIMENTAL: GROQ_MODELS.GPT_OSS_20B
1896
+ };
1882
1897
  var GROQ_BASE_URL = "https://api.groq.com/openai/v1";
1898
+ var DEFAULT_BASE_URL = GROQ_BASE_URL;
1883
1899
  var CHAT_COMPLETIONS_PATH = "/chat/completions";
1900
+ var API_ENDPOINTS = {
1901
+ CHAT: CHAT_COMPLETIONS_PATH
1902
+ };
1884
1903
  var GROQ_TIMEOUTS = {
1885
1904
  DEFAULT: 3e4,
1886
1905
  STREAMING: 6e4
1887
1906
  };
1907
+ var TIMEOUTS = GROQ_TIMEOUTS;
1888
1908
  var HTTP_HEADERS = {
1889
1909
  CONTENT_TYPE: "Content-Type",
1890
1910
  APPLICATION_JSON: "application/json",
@@ -1943,13 +1963,65 @@ var GroqChatRequestBuilder = class _GroqChatRequestBuilder {
1943
1963
  }
1944
1964
  };
1945
1965
 
1966
+ // src/providers/groq/groq-config-store.ts
1967
+ var GroqConfigStore = class {
1968
+ config = null;
1969
+ initialize(config) {
1970
+ const validation = validateApiKey(config.apiKey, { minLength: 1 });
1971
+ if (!validation.valid) {
1972
+ throw new GroqError("INVALID_API_KEY" /* INVALID_API_KEY */, validation.reason ?? "Invalid API key");
1973
+ }
1974
+ const normalized = {
1975
+ apiKey: config.apiKey.trim(),
1976
+ baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
1977
+ timeoutMs: config.timeoutMs ?? TIMEOUTS.DEFAULT,
1978
+ ...config.models !== void 0 ? { models: config.models } : {},
1979
+ ...config.textModel !== void 0 ? { textModel: config.textModel } : {}
1980
+ };
1981
+ this.config = normalized;
1982
+ return normalized;
1983
+ }
1984
+ get() {
1985
+ if (!this.config) {
1986
+ throw new GroqError(
1987
+ "MISSING_CONFIG" /* MISSING_CONFIG */,
1988
+ "Client not initialized. Call configureProvider first."
1989
+ );
1990
+ }
1991
+ return this.config;
1992
+ }
1993
+ getOrNull() {
1994
+ return this.config;
1995
+ }
1996
+ isInitialized() {
1997
+ return this.config !== null;
1998
+ }
1999
+ setApiKey(apiKey) {
2000
+ const current = this.get();
2001
+ const validation = validateApiKey(apiKey, { minLength: 1 });
2002
+ if (!validation.valid) {
2003
+ throw new GroqError("INVALID_API_KEY" /* INVALID_API_KEY */, validation.reason ?? "Invalid API key");
2004
+ }
2005
+ this.config = { ...current, apiKey: apiKey.trim() };
2006
+ }
2007
+ reset() {
2008
+ this.config = null;
2009
+ }
2010
+ };
2011
+
1946
2012
  // src/providers/groq/groq-http-transport.ts
1947
2013
  var GroqHttpTransport = class {
1948
- constructor(config) {
1949
- this.config = config;
2014
+ constructor(configSource) {
2015
+ this.configSource = configSource;
1950
2016
  }
1951
- config;
2017
+ configSource;
1952
2018
  pendingAbortControllers = /* @__PURE__ */ new Set();
2019
+ getConfig() {
2020
+ if ("get" in this.configSource && typeof this.configSource.get === "function") {
2021
+ return this.configSource.get();
2022
+ }
2023
+ return this.configSource;
2024
+ }
1953
2025
  cancelPendingRequests() {
1954
2026
  for (const controller of this.pendingAbortControllers) {
1955
2027
  controller.abort();
@@ -1964,7 +2036,7 @@ var GroqHttpTransport = class {
1964
2036
  }
1965
2037
  async streamChatCompletion(request, options = {}) {
1966
2038
  const url = `${this.baseUrl()}${CHAT_COMPLETIONS_PATH}`;
1967
- const timeout = this.config.timeoutMs ?? GROQ_TIMEOUTS.STREAMING;
2039
+ const timeout = this.getConfig().timeoutMs ?? GROQ_TIMEOUTS.STREAMING;
1968
2040
  const controller = new AbortController();
1969
2041
  const timeoutId = setTimeout(() => controller.abort(), timeout);
1970
2042
  const onExternalAbort = () => controller.abort();
@@ -1991,11 +2063,11 @@ var GroqHttpTransport = class {
1991
2063
  }
1992
2064
  }
1993
2065
  baseUrl() {
1994
- return this.config.baseUrl ?? GROQ_BASE_URL;
2066
+ return this.getConfig().baseUrl ?? GROQ_BASE_URL;
1995
2067
  }
1996
2068
  async request(endpoint, body) {
1997
2069
  const url = `${this.baseUrl()}${endpoint}`;
1998
- const timeout = this.config.timeoutMs ?? GROQ_TIMEOUTS.DEFAULT;
2070
+ const timeout = this.getConfig().timeoutMs ?? GROQ_TIMEOUTS.DEFAULT;
1999
2071
  const controller = new AbortController();
2000
2072
  const timeoutId = setTimeout(() => controller.abort(), timeout);
2001
2073
  this.pendingAbortControllers.add(controller);
@@ -2018,7 +2090,7 @@ var GroqHttpTransport = class {
2018
2090
  buildHeaders() {
2019
2091
  return {
2020
2092
  [HTTP_HEADERS.CONTENT_TYPE]: HTTP_HEADERS.APPLICATION_JSON,
2021
- [HTTP_HEADERS.AUTHORIZATION]: `${HTTP_HEADERS.BEARER_PREFIX}${this.config.apiKey}`
2093
+ [HTTP_HEADERS.AUTHORIZATION]: `${HTTP_HEADERS.BEARER_PREFIX}${this.getConfig().apiKey}`
2022
2094
  };
2023
2095
  }
2024
2096
  async throwFromErrorResponse(response) {
@@ -2062,21 +2134,40 @@ var GroqHttpTransport = class {
2062
2134
 
2063
2135
  // src/providers/groq/groq-http-client.ts
2064
2136
  var GroqHttpClient = class {
2137
+ configStore;
2065
2138
  transport;
2066
2139
  executor;
2067
- constructor(config, deps = {}) {
2068
- this.transport = deps.transport ?? new GroqHttpTransport(config);
2140
+ constructor(configOrDeps, maybeDeps = {}) {
2141
+ const isConfig = typeof configOrDeps === "object" && configOrDeps !== null && "apiKey" in configOrDeps && typeof configOrDeps.apiKey === "string";
2142
+ const deps = isConfig ? maybeDeps : configOrDeps ?? {};
2143
+ this.configStore = deps.configStore ?? new GroqConfigStore();
2144
+ if (isConfig) {
2145
+ this.configStore.initialize(configOrDeps);
2146
+ }
2147
+ this.transport = deps.transport ?? new GroqHttpTransport(this.configStore);
2069
2148
  const retryManager2 = deps.retryManager ?? new RetryManager();
2070
2149
  const queue = deps.queue ?? new RequestQueue();
2071
2150
  const deduplicator = deps.deduplicator ?? new RequestDeduplicator();
2072
2151
  this.executor = new CompositeExecutor([
2073
2152
  new DedupeExecutor(deduplicator, {
2074
- generateKey: (request) => deduplicatorKey(request, config.apiKey)
2153
+ generateKey: (request) => deduplicatorKey(request, this.configStore.isInitialized() ? this.configStore.get().apiKey : "")
2075
2154
  }),
2076
2155
  new RetryExecutor(retryManager2),
2077
2156
  new QueueExecutor(queue, QUEUE_PRIORITY_USER_REQUEST)
2078
2157
  ]);
2079
2158
  }
2159
+ initialize(config) {
2160
+ this.configStore.initialize(config);
2161
+ }
2162
+ isInitialized() {
2163
+ return this.configStore.isInitialized();
2164
+ }
2165
+ getApiKey() {
2166
+ return this.configStore.get().apiKey;
2167
+ }
2168
+ setApiKey(apiKey) {
2169
+ this.configStore.setApiKey(apiKey);
2170
+ }
2080
2171
  cancelPendingRequests() {
2081
2172
  this.transport.cancelPendingRequests();
2082
2173
  }
@@ -2089,6 +2180,10 @@ var GroqHttpClient = class {
2089
2180
  streamChatCompletion(request, options) {
2090
2181
  return this.transport.streamChatCompletion(request, options);
2091
2182
  }
2183
+ reset() {
2184
+ this.transport.cancelPendingRequests();
2185
+ this.configStore.reset();
2186
+ }
2092
2187
  };
2093
2188
  function deduplicatorKey(request, apiKey) {
2094
2189
  const { model, messages, stream: _stream, ...rest } = request;
@@ -2820,39 +2915,811 @@ function createFalProvider(config) {
2820
2915
  return new FalProvider(config);
2821
2916
  }
2822
2917
 
2823
- // src/providers/pruna/pruna-generate.ts
2824
- var prunaCorePromise = null;
2825
- async function loadPrunaCore() {
2826
- if (!prunaCorePromise) {
2827
- const specifier = "@umituz/pruna-provider/core";
2828
- prunaCorePromise = import(specifier);
2918
+ // src/providers/pruna/client/core/constants/limits.ts
2919
+ var DEFAULT_ASPECT_RATIO = "16:9";
2920
+ var P_VIDEO_DEFAULTS = {
2921
+ duration: 5,
2922
+ resolution: "720p",
2923
+ fps: 24,
2924
+ draft: false,
2925
+ promptUpsampling: true
2926
+ };
2927
+ var POLL_DEFAULTS = {
2928
+ intervalMs: 3e3,
2929
+ maxAttempts: 120,
2930
+ maxIntervalMs: 1e4,
2931
+ backoffBase: 1.5,
2932
+ jitterPercent: 0.1
2933
+ };
2934
+ var DEFAULT_DEDUPLICATOR_TTL_MS = 3e4;
2935
+ var DEDUPLICATOR_CLEANUP_GRACE_MS = 100;
2936
+
2937
+ // src/providers/pruna/client/core/constants/network.ts
2938
+ var HTTPS_PREFIX = "https://";
2939
+ var HTTP_PREFIX = "http://";
2940
+ var BASE64_MARKER = "base64,";
2941
+ var PRUNA_BASE_URL = "https://api.pruna.ai";
2942
+ var PRUNA_PREDICTIONS_URL = `${PRUNA_BASE_URL}/v1/predictions`;
2943
+ var PRUNA_FILES_URL = `${PRUNA_BASE_URL}/v1/files`;
2944
+ var AUTH_FAILURE_STATUSES = [401, 403];
2945
+ var MIN_API_KEY_LENGTH = 1;
2946
+ var MAX_API_KEY_LENGTH = 256;
2947
+
2948
+ // src/providers/pruna/client/core/constants/mime-types.ts
2949
+ var MIME_PNG = "image/png";
2950
+ var MIME_JPEG = "image/jpeg";
2951
+ var MIME_WEBP = "image/webp";
2952
+ var DEFAULT_IMAGE_MIME = MIME_PNG;
2953
+ var MIME_FILE_EXTENSION = {
2954
+ [MIME_PNG]: "png",
2955
+ [MIME_JPEG]: "jpg",
2956
+ [MIME_WEBP]: "webp"
2957
+ };
2958
+ var IMAGE_SIGNATURES = {
2959
+ PNG: [137, 80],
2960
+ JPEG: [255, 216],
2961
+ WEBP_RIFF: [82, 73]
2962
+ };
2963
+
2964
+ // src/providers/pruna/client/core/errors/cancellation-error.ts
2965
+ var CANCELLATION_MESSAGE = "Request cancelled by user";
2966
+ var CancellationError = class extends Error {
2967
+ constructor(message = CANCELLATION_MESSAGE) {
2968
+ super(message);
2969
+ this.name = "CancellationError";
2829
2970
  }
2830
- return prunaCorePromise;
2971
+ };
2972
+
2973
+ // src/providers/pruna/client/core/errors/authentication-error.ts
2974
+ var AUTHENTICATION_MESSAGE = "Authentication failed. Please check your API key.";
2975
+ var AuthenticationError = class extends Error {
2976
+ constructor(message = AUTHENTICATION_MESSAGE) {
2977
+ super(message);
2978
+ this.name = "AuthenticationError";
2979
+ }
2980
+ };
2981
+
2982
+ // src/providers/pruna/client/core/errors/timeout-error.ts
2983
+ var TIMEOUT_MESSAGE = "Generation timed out. Maximum polling attempts reached.";
2984
+ var TimeoutError = class extends Error {
2985
+ constructor(message = TIMEOUT_MESSAGE) {
2986
+ super(message);
2987
+ this.name = "TimeoutError";
2988
+ }
2989
+ };
2990
+
2991
+ // src/providers/pruna/client/core/errors/validation-error.ts
2992
+ var ValidationError = class extends Error {
2993
+ field;
2994
+ reason;
2995
+ receivedValue;
2996
+ constructor(context) {
2997
+ super(`Invalid ${context.field}: ${context.reason}`);
2998
+ this.name = "ValidationError";
2999
+ this.field = context.field;
3000
+ this.reason = context.reason;
3001
+ this.receivedValue = context.receivedValue;
3002
+ }
3003
+ };
3004
+
3005
+ // src/providers/pruna/client/core/utils/base64.ts
3006
+ var globalScope = globalThis;
3007
+ var hasBuffer = typeof globalScope.Buffer?.from === "function";
3008
+ function base64ToBytes(raw) {
3009
+ if (hasBuffer) {
3010
+ const bufferFactory = globalScope.Buffer;
3011
+ if (!bufferFactory) {
3012
+ throw new Error("Buffer unavailable despite detection flag");
3013
+ }
3014
+ const buf = bufferFactory.from(raw, "base64");
3015
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
3016
+ }
3017
+ const decoded = atob(raw);
3018
+ return Uint8Array.from(decoded, (char) => char.charCodeAt(0));
2831
3019
  }
2832
- async function* generateWithPruna(request, apiKey, signal, onProgress) {
2833
- const prunaCore = await loadPrunaCore();
2834
- if (prunaCore.generateWithPruna) {
2835
- yield* prunaCore.generateWithPruna(request, apiKey, signal, onProgress);
2836
- return;
2837
- }
2838
- if (!prunaCore.submitPrediction || !prunaCore.pollForResult) {
2839
- throw new Error("@umituz/pruna-provider/core does not export required functions");
2840
- }
2841
- const modelId = request.model || (request.image ? "p-image-edit" : "p-image");
2842
- const prediction = await prunaCore.submitPrediction(modelId, request, apiKey, signal, onProgress);
2843
- if (prediction.output && typeof prediction.output === "object" && "url" in prediction.output) {
2844
- yield {
2845
- stage: "predicting",
2846
- result: { url: prediction.output.url, model: modelId }
3020
+
3021
+ // src/providers/pruna/client/core/utils/mime-detector.ts
3022
+ var MIN_SIGNATURE_BYTES = 2;
3023
+ function matchesSignature(bytes, signature) {
3024
+ if (bytes.length < signature.length) return false;
3025
+ return signature.every((value, index) => bytes[index] === value);
3026
+ }
3027
+ function detectImageMime(bytes) {
3028
+ if (bytes.length < MIN_SIGNATURE_BYTES) return DEFAULT_IMAGE_MIME;
3029
+ if (matchesSignature(bytes, IMAGE_SIGNATURES.PNG)) return MIME_PNG;
3030
+ if (matchesSignature(bytes, IMAGE_SIGNATURES.JPEG)) return MIME_JPEG;
3031
+ if (matchesSignature(bytes, IMAGE_SIGNATURES.WEBP_RIFF)) return MIME_WEBP;
3032
+ return DEFAULT_IMAGE_MIME;
3033
+ }
3034
+
3035
+ // src/providers/pruna/client/core/utils/api-error-parser.ts
3036
+ async function parseApiError(response, fallback) {
3037
+ const payload = await response.json().catch(() => ({ message: response.statusText }));
3038
+ const message = payload.message ?? `${fallback}: ${response.status}`;
3039
+ const error = new Error(message);
3040
+ error.statusCode = response.status;
3041
+ return error;
3042
+ }
3043
+
3044
+ // src/providers/pruna/client/core/protocol/http-status.ts
3045
+ function isAuthFailureStatus(status) {
3046
+ return AUTH_FAILURE_STATUSES.includes(status);
3047
+ }
3048
+
3049
+ // src/providers/pruna/client/core/protocol/job-status.ts
3050
+ var TERMINAL_SUCCESS_STATUSES = ["succeeded", "completed"];
3051
+ var TERMINAL_FAILURE_STATUSES = ["failed"];
3052
+ function isTerminalSuccessStatus(status) {
3053
+ if (!status) return false;
3054
+ return TERMINAL_SUCCESS_STATUSES.includes(status);
3055
+ }
3056
+ function isTerminalFailureStatus(status) {
3057
+ if (!status) return false;
3058
+ return TERMINAL_FAILURE_STATUSES.includes(status);
3059
+ }
3060
+
3061
+ // src/providers/pruna/client/core/protocol/abort-signal-classifier.ts
3062
+ function isAbortError(err) {
3063
+ if (err instanceof DOMException && err.code === DOMException.ABORT_ERR) return true;
3064
+ if (err instanceof Error && err.name === "AbortError") return true;
3065
+ return false;
3066
+ }
3067
+
3068
+ // src/providers/pruna/client/core/protocol/pruna-url-builder.ts
3069
+ function isAbsoluteHttpUrl(value) {
3070
+ return value.startsWith(HTTPS_PREFIX) || value.startsWith(HTTP_PREFIX);
3071
+ }
3072
+ function isHttpsUrl(value) {
3073
+ return value.startsWith(HTTPS_PREFIX);
3074
+ }
3075
+ function buildPrunaApiUrl(pathOrUrl) {
3076
+ if (isAbsoluteHttpUrl(pathOrUrl)) return pathOrUrl;
3077
+ return `${PRUNA_BASE_URL}${pathOrUrl}`;
3078
+ }
3079
+ function buildFileUrlById(fileId) {
3080
+ return `${PRUNA_FILES_URL}/${fileId}`;
3081
+ }
3082
+
3083
+ // src/providers/pruna/client/core/math/exponential-backoff.ts
3084
+ function calculateExponentialBackoff2(inputs) {
3085
+ const {
3086
+ attempt,
3087
+ baseIntervalMs,
3088
+ maxIntervalMs = POLL_DEFAULTS.maxIntervalMs,
3089
+ jitterPercent = POLL_DEFAULTS.jitterPercent,
3090
+ growthBase = POLL_DEFAULTS.backoffBase
3091
+ } = inputs;
3092
+ const safeAttempt = Math.max(0, attempt);
3093
+ const safeBase = Math.max(0, baseIntervalMs);
3094
+ const exponentialDelay = Math.min(safeBase * Math.pow(growthBase, safeAttempt), maxIntervalMs);
3095
+ const jitter = exponentialDelay * jitterPercent * Math.random();
3096
+ return exponentialDelay + jitter;
3097
+ }
3098
+
3099
+ // src/providers/pruna/client/core/utils/uri-resolver.ts
3100
+ var uriCache = /* @__PURE__ */ new WeakMap();
3101
+ function stripBase64Prefix(image) {
3102
+ if (isHttpsUrl(image)) return image;
3103
+ const base64Index = image.indexOf(BASE64_MARKER);
3104
+ return base64Index !== -1 ? image.slice(base64Index + BASE64_MARKER.length) : image;
3105
+ }
3106
+ function readOutput(output) {
3107
+ if (typeof output === "string") return output;
3108
+ if (Array.isArray(output) && output.length > 0) return output[0] ?? null;
3109
+ if (output !== null && typeof output === "object" && "url" in output) {
3110
+ return output.url;
3111
+ }
3112
+ return null;
3113
+ }
3114
+ function extractUri(data) {
3115
+ const cached = uriCache.get(data);
3116
+ if (cached !== void 0) return cached;
3117
+ if (!isTerminalSuccessStatus(data.status)) {
3118
+ uriCache.set(data, null);
3119
+ return null;
3120
+ }
3121
+ const result = data.generation_url ?? readOutput(data.output) ?? data.video_url ?? data.data ?? null;
3122
+ uriCache.set(data, result);
3123
+ return result;
3124
+ }
3125
+ function resolveUri(uri) {
3126
+ return uri.startsWith("/") ? `${PRUNA_BASE_URL}${uri}` : uri;
3127
+ }
3128
+
3129
+ // src/providers/pruna/client/core/services/pruna-client.service.ts
3130
+ function buildFileExtension(mime) {
3131
+ return MIME_FILE_EXTENSION[mime] ?? "bin";
3132
+ }
3133
+ async function uploadImage(base64OrUrl, apiKey, onProgress) {
3134
+ if (isHttpsUrl(base64OrUrl)) return base64OrUrl;
3135
+ if (base64OrUrl.startsWith(HTTP_PREFIX)) {
3136
+ throw new Error("Only HTTPS URLs are supported for security.");
3137
+ }
3138
+ onProgress?.("uploading");
3139
+ const raw = stripBase64Prefix(base64OrUrl);
3140
+ let bytes;
3141
+ try {
3142
+ bytes = base64ToBytes(raw);
3143
+ } catch {
3144
+ throw new Error("Invalid image format. Provide base64 or a valid HTTPS URL.");
3145
+ }
3146
+ const mime = detectImageMime(bytes);
3147
+ const buffer = bytes.buffer.slice(
3148
+ bytes.byteOffset,
3149
+ bytes.byteOffset + bytes.byteLength
3150
+ );
3151
+ const blob = new Blob([buffer], { type: mime });
3152
+ const formData = new FormData();
3153
+ formData.append("content", blob, `upload.${buildFileExtension(mime)}`);
3154
+ const res = await fetch(PRUNA_FILES_URL, {
3155
+ method: "POST",
3156
+ headers: { apikey: apiKey },
3157
+ body: formData
3158
+ });
3159
+ if (!res.ok) {
3160
+ throw await parseApiError(res, "File upload error");
3161
+ }
3162
+ const data = await res.json();
3163
+ return data.urls?.get ?? buildFileUrlById(data.id ?? "");
3164
+ }
3165
+ async function submitPrediction(model, input, apiKey, signal, onProgress) {
3166
+ onProgress?.("predicting");
3167
+ const res = await fetch(PRUNA_PREDICTIONS_URL, {
3168
+ method: "POST",
3169
+ headers: {
3170
+ apikey: apiKey,
3171
+ Model: model,
3172
+ "Try-Sync": "true",
3173
+ "Content-Type": "application/json"
3174
+ },
3175
+ body: JSON.stringify({ input }),
3176
+ signal
3177
+ });
3178
+ if (!res.ok) {
3179
+ throw await parseApiError(res, "API error");
3180
+ }
3181
+ return res.json();
3182
+ }
3183
+ var GenerationFailedError = class extends Error {
3184
+ constructor(message) {
3185
+ super(message);
3186
+ this.name = "GenerationFailedError";
3187
+ }
3188
+ };
3189
+ async function attemptPoll(fullUrl, apiKey, signal) {
3190
+ let res;
3191
+ try {
3192
+ res = await fetch(fullUrl, { headers: { apikey: apiKey }, signal });
3193
+ } catch (err) {
3194
+ if (isAbortError(err)) throw new CancellationError();
3195
+ return { outcome: "transient-failure" };
3196
+ }
3197
+ if (!res.ok) {
3198
+ if (isAuthFailureStatus(res.status)) throw new AuthenticationError();
3199
+ return { outcome: "transient-failure" };
3200
+ }
3201
+ let data;
3202
+ try {
3203
+ data = await res.json();
3204
+ } catch {
3205
+ return { outcome: "transient-failure" };
3206
+ }
3207
+ if (isTerminalSuccessStatus(data.status)) {
3208
+ const uri = extractUri(data);
3209
+ if (uri) return { outcome: "succeeded", uri: resolveUri(uri) };
3210
+ return { outcome: "transient-failure" };
3211
+ }
3212
+ if (isTerminalFailureStatus(data.status)) {
3213
+ throw new GenerationFailedError(data.error ?? "Generation failed during processing.");
3214
+ }
3215
+ return { outcome: "continue" };
3216
+ }
3217
+ async function pollForResult(pollUrl, apiKey, maxAttempts = POLL_DEFAULTS.maxAttempts, intervalMs = POLL_DEFAULTS.intervalMs, signal, onProgress) {
3218
+ const fullUrl = isAbsoluteHttpUrl(pollUrl) ? pollUrl : buildPrunaApiUrl(pollUrl);
3219
+ const controller = new AbortController();
3220
+ const onExternalAbort = () => controller.abort();
3221
+ signal?.addEventListener("abort", onExternalAbort);
3222
+ try {
3223
+ for (let i = 0; i < maxAttempts; i++) {
3224
+ if (signal?.aborted) throw new CancellationError();
3225
+ const delay = calculateExponentialBackoff2({ attempt: i, baseIntervalMs: intervalMs });
3226
+ await new Promise((resolve) => setTimeout(resolve, delay));
3227
+ if (signal?.aborted) throw new CancellationError();
3228
+ onProgress?.("polling", i + 1);
3229
+ const result = await attemptPoll(fullUrl, apiKey, controller.signal);
3230
+ if (result.outcome === "succeeded" && result.uri) {
3231
+ return result.uri;
3232
+ }
3233
+ }
3234
+ throw new TimeoutError();
3235
+ } finally {
3236
+ signal?.removeEventListener("abort", onExternalAbort);
3237
+ controller.abort();
3238
+ }
3239
+ }
3240
+
3241
+ // src/providers/pruna/client/core/math/comparison-hash.ts
3242
+ function sortKeysDeep(value) {
3243
+ if (Array.isArray(value)) {
3244
+ return value.map(sortKeysDeep);
3245
+ }
3246
+ if (value !== null && typeof value === "object") {
3247
+ const record = value;
3248
+ return Object.keys(record).sort().reduce((acc, key) => {
3249
+ acc[key] = sortKeysDeep(record[key]);
3250
+ return acc;
3251
+ }, {});
3252
+ }
3253
+ return value;
3254
+ }
3255
+ function hashValue(value) {
3256
+ if (value === null || typeof value !== "object") {
3257
+ return String(value);
3258
+ }
3259
+ try {
3260
+ return JSON.stringify(sortKeysDeep(value));
3261
+ } catch {
3262
+ return String(value);
3263
+ }
3264
+ }
3265
+ function compositeHash(...values) {
3266
+ return values.map(hashValue).join("|");
3267
+ }
3268
+
3269
+ // src/providers/pruna/client/core/math/cache-key-derivation.ts
3270
+ function deriveGenerationCacheKey(apiKey, input) {
3271
+ return `${apiKey}:${compositeHash(input)}`;
3272
+ }
3273
+
3274
+ // src/providers/pruna/client/core/validation/api-key-validator.ts
3275
+ function assertValidApiKey(apiKey) {
3276
+ if (typeof apiKey !== "string") {
3277
+ throw new ValidationError({
3278
+ field: "apiKey",
3279
+ reason: "must be a string",
3280
+ receivedValue: typeof apiKey
3281
+ });
3282
+ }
3283
+ if (apiKey.length < MIN_API_KEY_LENGTH) {
3284
+ throw new ValidationError({
3285
+ field: "apiKey",
3286
+ reason: "must not be empty",
3287
+ receivedValue: apiKey
3288
+ });
3289
+ }
3290
+ if (apiKey.length > MAX_API_KEY_LENGTH) {
3291
+ throw new ValidationError({
3292
+ field: "apiKey",
3293
+ reason: `exceeds maximum length of ${MAX_API_KEY_LENGTH}`,
3294
+ receivedValue: apiKey.length
3295
+ });
3296
+ }
3297
+ }
3298
+
3299
+ // src/providers/pruna/client/core/validation/image-source-validator.ts
3300
+ function classifyImageSource(source) {
3301
+ if (source.startsWith(HTTPS_PREFIX)) return "https-url";
3302
+ if (source.startsWith(HTTP_PREFIX)) return "http-url";
3303
+ if (source.includes(BASE64_MARKER) || /^[A-Za-z0-9+/=]+$/.test(source)) return "base64";
3304
+ return "unknown";
3305
+ }
3306
+ function assertSecureImageSource(image, fieldName = "image") {
3307
+ if (typeof image !== "string") {
3308
+ throw new ValidationError({
3309
+ field: fieldName,
3310
+ reason: "must be a string",
3311
+ receivedValue: typeof image
3312
+ });
3313
+ }
3314
+ const kind = classifyImageSource(image);
3315
+ if (kind === "http-url") {
3316
+ throw new ValidationError({
3317
+ field: fieldName,
3318
+ reason: "HTTP URLs are not supported; use HTTPS or a base64 data URI",
3319
+ receivedValue: image
3320
+ });
3321
+ }
3322
+ if (kind === "unknown") {
3323
+ throw new ValidationError({
3324
+ field: fieldName,
3325
+ reason: "must be an HTTPS URL or a base64 data URI",
3326
+ receivedValue: typeof image
3327
+ });
3328
+ }
3329
+ }
3330
+
3331
+ // src/providers/pruna/client/core/validation/aspect-ratio-validator.ts
3332
+ var SUPPORTED_ASPECT_RATIOS = [
3333
+ "16:9",
3334
+ "9:16",
3335
+ "1:1",
3336
+ "4:3",
3337
+ "3:4",
3338
+ "3:2",
3339
+ "2:3"
3340
+ ];
3341
+ function isSupportedAspectRatio(value) {
3342
+ return typeof value === "string" && SUPPORTED_ASPECT_RATIOS.includes(value);
3343
+ }
3344
+ function assertValidAspectRatio(value, fieldName = "aspect_ratio") {
3345
+ if (value === void 0 || value === null) return;
3346
+ if (!isSupportedAspectRatio(value)) {
3347
+ throw new ValidationError({
3348
+ field: fieldName,
3349
+ reason: `must be one of ${SUPPORTED_ASPECT_RATIOS.join(", ")}`,
3350
+ receivedValue: value
3351
+ });
3352
+ }
3353
+ }
3354
+
3355
+ // src/providers/pruna/client/core/validation/resolution-validator.ts
3356
+ var SUPPORTED_RESOLUTIONS = ["720p", "1080p"];
3357
+ function isSupportedResolution(value) {
3358
+ return typeof value === "string" && SUPPORTED_RESOLUTIONS.includes(value);
3359
+ }
3360
+ function assertValidResolution(value, fieldName = "resolution") {
3361
+ if (value === void 0 || value === null) return;
3362
+ if (!isSupportedResolution(value)) {
3363
+ throw new ValidationError({
3364
+ field: fieldName,
3365
+ reason: `must be one of ${SUPPORTED_RESOLUTIONS.join(", ")}`,
3366
+ receivedValue: value
3367
+ });
3368
+ }
3369
+ }
3370
+
3371
+ // src/providers/pruna/client/core/validation/duration-validator.ts
3372
+ var MIN_VIDEO_DURATION_SECONDS = 1;
3373
+ var MAX_VIDEO_DURATION_SECONDS = 30;
3374
+ function isValidDuration(value) {
3375
+ return typeof value === "number" && Number.isFinite(value) && value >= MIN_VIDEO_DURATION_SECONDS && value <= MAX_VIDEO_DURATION_SECONDS && Number.isInteger(value);
3376
+ }
3377
+ function assertValidDuration(value, fieldName = "duration") {
3378
+ if (value === void 0 || value === null) return;
3379
+ if (!isValidDuration(value)) {
3380
+ throw new ValidationError({
3381
+ field: fieldName,
3382
+ reason: `must be an integer between ${MIN_VIDEO_DURATION_SECONDS} and ${MAX_VIDEO_DURATION_SECONDS} seconds`,
3383
+ receivedValue: value
3384
+ });
3385
+ }
3386
+ }
3387
+
3388
+ // src/providers/pruna/client/core/validation/prompt-validator.ts
3389
+ var MIN_PROMPT_LENGTH = 1;
3390
+ var MAX_PROMPT_LENGTH = 4e3;
3391
+ function assertValidPrompt(value, fieldName = "prompt") {
3392
+ if (typeof value !== "string") {
3393
+ throw new ValidationError({
3394
+ field: fieldName,
3395
+ reason: "must be a string",
3396
+ receivedValue: typeof value
3397
+ });
3398
+ }
3399
+ if (value.trim().length < MIN_PROMPT_LENGTH) {
3400
+ throw new ValidationError({
3401
+ field: fieldName,
3402
+ reason: "must not be empty or whitespace-only",
3403
+ receivedValue: value
3404
+ });
3405
+ }
3406
+ if (value.length > MAX_PROMPT_LENGTH) {
3407
+ throw new ValidationError({
3408
+ field: fieldName,
3409
+ reason: `exceeds maximum length of ${MAX_PROMPT_LENGTH} characters`,
3410
+ receivedValue: value.length
3411
+ });
3412
+ }
3413
+ }
3414
+
3415
+ // src/providers/pruna/client/core/validation/seed-validator.ts
3416
+ var MIN_SEED = -2147483648;
3417
+ var MAX_SEED = 2147483647;
3418
+ function isValidSeed(value) {
3419
+ return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= MIN_SEED && value <= MAX_SEED;
3420
+ }
3421
+ function assertValidSeed(value, fieldName = "seed") {
3422
+ if (value === void 0 || value === null) return;
3423
+ if (!isValidSeed(value)) {
3424
+ throw new ValidationError({
3425
+ field: fieldName,
3426
+ reason: `must be an integer in [${MIN_SEED}, ${MAX_SEED}]`,
3427
+ receivedValue: value
3428
+ });
3429
+ }
3430
+ }
3431
+
3432
+ // src/providers/pruna/client/core/protocol/input-type-guard.ts
3433
+ function isTextToImageInput(input) {
3434
+ return input.model === "p-image";
3435
+ }
3436
+ function isImageToImageInput(input) {
3437
+ return input.model === "p-image-edit";
3438
+ }
3439
+ function isImageToVideoInput(input) {
3440
+ return input.model === "p-video";
3441
+ }
3442
+
3443
+ // src/providers/pruna/client/core/utils/request-deduplicator.ts
3444
+ var RequestDeduplicator2 = class {
3445
+ pendingRequests = /* @__PURE__ */ new Map();
3446
+ ttl;
3447
+ constructor(ttl = DEFAULT_DEDUPLICATOR_TTL_MS) {
3448
+ this.ttl = ttl;
3449
+ }
3450
+ execute(key, fn) {
3451
+ const existing = this.pendingRequests.get(key);
3452
+ if (existing) {
3453
+ if (Date.now() - existing.timestamp < this.ttl) {
3454
+ return existing.promise;
3455
+ }
3456
+ this.pendingRequests.delete(key);
3457
+ }
3458
+ const promise = fn().finally(() => {
3459
+ setTimeout(() => {
3460
+ this.pendingRequests.delete(key);
3461
+ }, DEDUPLICATOR_CLEANUP_GRACE_MS);
3462
+ });
3463
+ this.pendingRequests.set(key, { promise, timestamp: Date.now() });
3464
+ return promise;
3465
+ }
3466
+ clear() {
3467
+ this.pendingRequests.clear();
3468
+ }
3469
+ size() {
3470
+ this.purgeExpired();
3471
+ return this.pendingRequests.size;
3472
+ }
3473
+ purgeExpired() {
3474
+ const now = Date.now();
3475
+ for (const [key, value] of this.pendingRequests) {
3476
+ if (now - value.timestamp >= this.ttl) {
3477
+ this.pendingRequests.delete(key);
3478
+ }
3479
+ }
3480
+ }
3481
+ };
3482
+ var globalDeduplicator = new RequestDeduplicator2();
3483
+
3484
+ // src/providers/pruna/client/generation/builders/text-to-image.builder.ts
3485
+ var TextToImageInputBuilder = class {
3486
+ model = "p-image";
3487
+ build(input, _context) {
3488
+ const payload = {
3489
+ prompt: input.prompt,
3490
+ aspect_ratio: input.aspect_ratio ?? DEFAULT_ASPECT_RATIO
3491
+ };
3492
+ if (input.seed !== void 0) {
3493
+ payload.seed = input.seed;
3494
+ }
3495
+ return Promise.resolve(payload);
3496
+ }
3497
+ };
3498
+
3499
+ // src/providers/pruna/client/generation/builders/image-to-image.builder.ts
3500
+ var ImageToImageInputBuilder = class {
3501
+ model = "p-image-edit";
3502
+ build(input, _context) {
3503
+ const payload = {
3504
+ images: [stripBase64Prefix(input.image)],
3505
+ prompt: input.prompt,
3506
+ aspect_ratio: input.aspect_ratio ?? DEFAULT_ASPECT_RATIO
3507
+ };
3508
+ if (input.seed !== void 0) {
3509
+ payload.seed = input.seed;
3510
+ }
3511
+ return Promise.resolve(payload);
3512
+ }
3513
+ };
3514
+
3515
+ // src/providers/pruna/client/generation/builders/image-to-video.builder.ts
3516
+ var ImageToVideoInputBuilder = class {
3517
+ model = "p-video";
3518
+ async build(input, context) {
3519
+ if (context.signal?.aborted) {
3520
+ throw new CancellationError();
3521
+ }
3522
+ const fileUrl = await uploadImage(
3523
+ input.image,
3524
+ context.apiKey,
3525
+ context.onProgress
3526
+ );
3527
+ return {
3528
+ image: fileUrl,
3529
+ prompt: input.prompt,
3530
+ duration: input.duration ?? P_VIDEO_DEFAULTS.duration,
3531
+ resolution: input.resolution ?? P_VIDEO_DEFAULTS.resolution,
3532
+ fps: P_VIDEO_DEFAULTS.fps,
3533
+ draft: input.draft ?? P_VIDEO_DEFAULTS.draft,
3534
+ aspect_ratio: input.aspect_ratio ?? DEFAULT_ASPECT_RATIO,
3535
+ prompt_upsampling: P_VIDEO_DEFAULTS.promptUpsampling
2847
3536
  };
2848
- return;
2849
3537
  }
2850
- const pollUrl = prediction.status_url || prediction.generation_url;
3538
+ };
3539
+
3540
+ // src/providers/pruna/client/generation/builders/model-input-builder.ts
3541
+ var ModelInputBuilderRegistry = class {
3542
+ builders = /* @__PURE__ */ new Map();
3543
+ register(builder) {
3544
+ this.builders.set(builder.model, builder);
3545
+ return this;
3546
+ }
3547
+ resolve(model) {
3548
+ const builder = this.builders.get(model);
3549
+ if (!builder) {
3550
+ throw new Error(`No input builder registered for model: ${model}`);
3551
+ }
3552
+ return builder;
3553
+ }
3554
+ };
3555
+
3556
+ // src/providers/pruna/client/generation/builders/builder-factory.ts
3557
+ function createDefaultBuilderRegistry() {
3558
+ return new ModelInputBuilderRegistry().register(new TextToImageInputBuilder()).register(new ImageToImageInputBuilder()).register(new ImageToVideoInputBuilder());
3559
+ }
3560
+
3561
+ // src/providers/pruna/client/generation/services/generation.service.ts
3562
+ var builderRegistry = createDefaultBuilderRegistry();
3563
+ function validateGenerateInput(input) {
3564
+ assertValidPrompt(input.prompt, `${input.model}.prompt`);
3565
+ assertValidAspectRatio(input.aspect_ratio, `${input.model}.aspect_ratio`);
3566
+ if (isTextToImageInput(input)) {
3567
+ assertValidSeed(input.seed, `${input.model}.seed`);
3568
+ } else if (isImageToImageInput(input)) {
3569
+ assertSecureImageSource(input.image, `${input.model}.image`);
3570
+ assertValidSeed(input.seed, `${input.model}.seed`);
3571
+ } else if (isImageToVideoInput(input)) {
3572
+ assertSecureImageSource(input.image, `${input.model}.image`);
3573
+ assertValidDuration(input.duration, `${input.model}.duration`);
3574
+ assertValidResolution(input.resolution, `${input.model}.resolution`);
3575
+ }
3576
+ }
3577
+ async function generate(apiKey, input, options = {}) {
3578
+ assertValidApiKey(apiKey);
3579
+ validateGenerateInput(input);
3580
+ const { signal, onProgress, useCache = true } = options;
3581
+ if (useCache) {
3582
+ const cacheKey = deriveGenerationCacheKey(apiKey, input);
3583
+ return globalDeduplicator.execute(
3584
+ cacheKey,
3585
+ () => generateInternal(apiKey, input, { signal, onProgress })
3586
+ );
3587
+ }
3588
+ return generateInternal(apiKey, input, { signal, onProgress });
3589
+ }
3590
+ async function generateInternal(apiKey, input, options) {
3591
+ const { signal, onProgress } = options;
3592
+ const builder = builderRegistry.resolve(input.model);
3593
+ const modelInput = await builder.build(input, { apiKey, signal, onProgress });
3594
+ const response = await submitPrediction(input.model, modelInput, apiKey, signal, onProgress);
3595
+ const syncUri = extractUri(response);
3596
+ if (syncUri) {
3597
+ return { url: resolveUri(syncUri), model: input.model };
3598
+ }
3599
+ const pollUrl = response.get_url ?? response.status_url;
2851
3600
  if (!pollUrl) {
2852
- throw new Error("Pruna prediction response missing polling URL");
3601
+ throw new Error("Pruna API returned no result and no polling URL.");
3602
+ }
3603
+ const url = await pollForResult(
3604
+ pollUrl,
3605
+ apiKey,
3606
+ POLL_DEFAULTS.maxAttempts,
3607
+ POLL_DEFAULTS.intervalMs,
3608
+ signal,
3609
+ onProgress
3610
+ );
3611
+ return { url, model: input.model };
3612
+ }
3613
+ async function generateImageThenVideo(apiKey, input, options = {}) {
3614
+ assertValidApiKey(apiKey);
3615
+ assertValidPrompt(input.prompt, "text-to-video.prompt");
3616
+ assertValidAspectRatio(input.aspect_ratio, "text-to-video.aspect_ratio");
3617
+ assertValidDuration(input.duration, "text-to-video.duration");
3618
+ assertValidResolution(input.resolution, "text-to-video.resolution");
3619
+ const { signal, onProgress } = options;
3620
+ const aspectRatio = input.aspect_ratio ?? DEFAULT_ASPECT_RATIO;
3621
+ onProgress?.("predicting", 1);
3622
+ const imageResult = await generate(
3623
+ apiKey,
3624
+ { model: "p-image", prompt: input.prompt, aspect_ratio: aspectRatio },
3625
+ { signal }
3626
+ );
3627
+ onProgress?.("predicting", 2);
3628
+ return generate(
3629
+ apiKey,
3630
+ {
3631
+ model: "p-video",
3632
+ prompt: input.prompt,
3633
+ image: imageResult.url,
3634
+ duration: input.duration ?? P_VIDEO_DEFAULTS.duration,
3635
+ resolution: input.resolution ?? P_VIDEO_DEFAULTS.resolution,
3636
+ aspect_ratio: aspectRatio,
3637
+ draft: input.draft ?? P_VIDEO_DEFAULTS.draft
3638
+ },
3639
+ { signal, onProgress }
3640
+ );
3641
+ }
3642
+
3643
+ // src/providers/pruna/pruna-generate.ts
3644
+ async function* generateWithPruna(request, apiKey, signal, onProgress) {
3645
+ const stages = [];
3646
+ let notify = null;
3647
+ let done = false;
3648
+ const wake = () => {
3649
+ const fn = notify;
3650
+ if (fn) {
3651
+ notify = null;
3652
+ fn();
3653
+ }
3654
+ };
3655
+ const handleProgress = (stage) => {
3656
+ stages.push({ stage });
3657
+ onProgress?.(stage);
3658
+ wake();
3659
+ };
3660
+ const promise = (async () => {
3661
+ try {
3662
+ const model = request.model ?? (request.image ? "p-image-edit" : "p-image");
3663
+ if (model === "p-video" && !request.image && request.prompt) {
3664
+ const videoRes = await generateImageThenVideo(
3665
+ apiKey,
3666
+ {
3667
+ prompt: request.prompt,
3668
+ duration: request.duration ?? 5,
3669
+ aspect_ratio: request.aspectRatio ?? request.aspect_ratio ?? "16:9"
3670
+ },
3671
+ { signal, onProgress: handleProgress }
3672
+ );
3673
+ stages.push({
3674
+ stage: "polling",
3675
+ result: { url: videoRes.url, model: "p-video" }
3676
+ });
3677
+ return;
3678
+ }
3679
+ const input = {
3680
+ model,
3681
+ prompt: request.prompt ?? "",
3682
+ aspect_ratio: request.aspectRatio ?? request.aspect_ratio ?? "1:1",
3683
+ ...request.image ? { image: request.image } : {},
3684
+ ...request.duration ? { duration: request.duration } : {},
3685
+ ...request.resolution ? { resolution: request.resolution } : {},
3686
+ ...request.seed !== void 0 ? { seed: request.seed } : {}
3687
+ };
3688
+ const res = await generate(apiKey, input, {
3689
+ signal,
3690
+ onProgress: handleProgress
3691
+ });
3692
+ stages.push({
3693
+ stage: "polling",
3694
+ result: { url: res.url, model: res.model }
3695
+ });
3696
+ } catch (err) {
3697
+ stages.push({
3698
+ stage: "processing",
3699
+ error: err instanceof Error ? err : new Error(String(err))
3700
+ });
3701
+ } finally {
3702
+ done = true;
3703
+ wake();
3704
+ }
3705
+ })();
3706
+ while (!done || stages.length > 0) {
3707
+ if (stages.length === 0 && !done) {
3708
+ await new Promise((resolve) => {
3709
+ notify = resolve;
3710
+ });
3711
+ notify = null;
3712
+ }
3713
+ while (stages.length > 0) {
3714
+ const nextStage = stages.shift();
3715
+ if (!nextStage) continue;
3716
+ yield nextStage;
3717
+ if (nextStage.error) {
3718
+ throw nextStage.error;
3719
+ }
3720
+ }
2853
3721
  }
2854
- const resultUrl = await prunaCore.pollForResult(pollUrl, apiKey, 60, 2e3, signal, onProgress);
2855
- yield { stage: "polling", result: { url: resultUrl, model: modelId } };
3722
+ await promise;
2856
3723
  }
2857
3724
 
2858
3725
  // src/providers/pruna/pruna.provider.ts
@@ -2965,6 +3832,278 @@ function createAI(config = {}) {
2965
3832
  return factory;
2966
3833
  }
2967
3834
 
3835
+ // src/providers/groq/groq-messages.ts
3836
+ var createUserMessage = (content) => ({
3837
+ role: "user",
3838
+ content
3839
+ });
3840
+ var createAssistantMessage = (content) => ({
3841
+ role: "assistant",
3842
+ content
3843
+ });
3844
+ var createSystemMessage = (content) => ({
3845
+ role: "system",
3846
+ content
3847
+ });
3848
+ var createMessage = (role, content) => ({
3849
+ role,
3850
+ content
3851
+ });
3852
+ var promptToMessages = (prompt) => [
3853
+ createUserMessage(prompt)
3854
+ ];
3855
+ var extractTextFromMessages = (messages) => messages.map((m) => m.content).join("\n\n");
3856
+ var formatMessagesForDisplay = (messages) => messages.map((m) => `[${m.role.toUpperCase()}]: ${m.content}`).join("\n\n---\n\n");
3857
+
3858
+ // src/providers/groq/groq-structured-json.ts
3859
+ async function requestStructuredJson(client, request) {
3860
+ const chatRequest = {
3861
+ model: request.model,
3862
+ messages: [createUserMessage(request.instructions)],
3863
+ temperature: request.temperature,
3864
+ max_tokens: request.maxTokens,
3865
+ response_format: { type: "json_object" },
3866
+ ...request.reasoningEffort !== void 0 ? { reasoning_effort: request.reasoningEffort } : {}
3867
+ };
3868
+ const response = await client.postChatCompletion(chatRequest);
3869
+ const content = response.choices[0]?.message?.content ?? "";
3870
+ const candidate = extractJsonCandidate(content);
3871
+ try {
3872
+ return JSON.parse(candidate);
3873
+ } catch (err) {
3874
+ throw new GroqError(
3875
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
3876
+ "model response contained no parsable json object",
3877
+ err instanceof Error ? err : void 0
3878
+ );
3879
+ }
3880
+ }
3881
+
3882
+ // src/providers/groq/text-generation.service.ts
3883
+ var STRUCTURED_OUTPUT_TEMPERATURE = 0.1;
3884
+ var TextGenerationService = class {
3885
+ constructor(httpClient, cache = new CacheManager(), defaultModel = DEFAULT_MODELS.TEXT) {
3886
+ this.httpClient = httpClient;
3887
+ this.cache = cache;
3888
+ this.defaultModel = defaultModel;
3889
+ }
3890
+ httpClient;
3891
+ cache;
3892
+ defaultModel;
3893
+ id = "groq-text-generation";
3894
+ name = "Groq Text Generation";
3895
+ capabilities = ["text"];
3896
+ jsonParser = new JsonResponseParser();
3897
+ generateCompletion(prompt, options = {}) {
3898
+ const model = options.model ?? this.defaultModel;
3899
+ return this.executeWithCache(
3900
+ {
3901
+ type: CACHE_KEY_TYPE_COMPLETION,
3902
+ model,
3903
+ prompt,
3904
+ config: options.generationConfig
3905
+ },
3906
+ async () => {
3907
+ const request = GroqChatRequestBuilder.for(model).withMessages([createUserMessage(prompt)]).withConfig(options.generationConfig).build();
3908
+ const response = await this.httpClient.postChatCompletion(request);
3909
+ return this.extractContent(response.choices[0]?.message?.content);
3910
+ }
3911
+ );
3912
+ }
3913
+ generateChatCompletion(messages, options = {}) {
3914
+ const model = options.model ?? this.defaultModel;
3915
+ return this.executeWithCache(
3916
+ {
3917
+ type: CACHE_KEY_TYPE_CHAT,
3918
+ model,
3919
+ messages: [...messages],
3920
+ config: options.generationConfig
3921
+ },
3922
+ async () => {
3923
+ const request = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig(options.generationConfig).build();
3924
+ const response = await this.httpClient.postChatCompletion(request);
3925
+ return this.extractContent(response.choices[0]?.message?.content);
3926
+ }
3927
+ );
3928
+ }
3929
+ generateStructured(promptOrRequest, options) {
3930
+ if (typeof promptOrRequest === "string") {
3931
+ const model2 = options?.model ?? this.defaultModel;
3932
+ const jsonPrompt = StructuredPromptBuilder.for(promptOrRequest).withSchema(options?.schema).build();
3933
+ const request = GroqChatRequestBuilder.for(model2).withMessages([createUserMessage(jsonPrompt)]).withConfig({
3934
+ ...options?.generationConfig,
3935
+ temperature: STRUCTURED_OUTPUT_TEMPERATURE
3936
+ }).build();
3937
+ return this.httpClient.postChatCompletion(request).then((response) => {
3938
+ const content = this.extractContent(response.choices[0]?.message?.content);
3939
+ return this.jsonParser.parse(content);
3940
+ });
3941
+ }
3942
+ const req = promptOrRequest;
3943
+ const model = req.model ?? (req.tier ? this.resolveModel(req.tier) : this.defaultModel);
3944
+ const schemaObj = req.schema;
3945
+ const messages = [];
3946
+ if (req.systemPrompt) {
3947
+ messages.push({ role: "system", content: req.systemPrompt });
3948
+ }
3949
+ for (const m of req.messages) {
3950
+ messages.push({ role: m.role, content: m.content });
3951
+ }
3952
+ const lastMsg = messages[messages.length - 1];
3953
+ if (lastMsg) {
3954
+ const structuredPrompt = StructuredPromptBuilder.for(lastMsg.content).withSchema(schemaObj).build();
3955
+ messages[messages.length - 1] = { ...lastMsg, content: structuredPrompt };
3956
+ }
3957
+ const groqReq = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig({
3958
+ temperature: req.temperature ?? STRUCTURED_OUTPUT_TEMPERATURE,
3959
+ maxTokens: req.maxTokens
3960
+ }).build();
3961
+ return this.httpClient.postChatCompletion(groqReq).then((response) => {
3962
+ const content = this.extractContent(response.choices[0]?.message?.content);
3963
+ const parsed = this.jsonParser.parse(content);
3964
+ const genResult = {
3965
+ text: content,
3966
+ model: response.model,
3967
+ providerId: this.id,
3968
+ finishReason: response.choices[0]?.finish_reason ?? "stop",
3969
+ usage: response.usage ? {
3970
+ inputTokens: response.usage.prompt_tokens,
3971
+ outputTokens: response.usage.completion_tokens,
3972
+ totalTokens: response.usage.total_tokens
3973
+ } : void 0
3974
+ };
3975
+ return { value: parsed, result: genResult };
3976
+ });
3977
+ }
3978
+ streamCompletion(prompt, callbacks, options = {}) {
3979
+ return this.streamChatCompletion([createUserMessage(prompt)], callbacks, options);
3980
+ }
3981
+ async streamChatCompletion(messages, callbacks, options = {}) {
3982
+ const model = options.model ?? this.defaultModel;
3983
+ const request = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig(options.generationConfig).withStream(true).build();
3984
+ const parser = new StreamingResponseParser(
3985
+ {
3986
+ onChunk: (chunk) => callbacks.onChunk?.(chunk)
3987
+ },
3988
+ extractContentFromSsePayload
3989
+ );
3990
+ try {
3991
+ const stream = await this.httpClient.streamChatCompletion(request, {
3992
+ signal: options.signal
3993
+ });
3994
+ const fullText = await parser.parse(stream);
3995
+ callbacks.onComplete?.(fullText);
3996
+ return fullText;
3997
+ } catch (error) {
3998
+ const err = error instanceof Error ? error : new Error(String(error));
3999
+ callbacks.onError?.(err);
4000
+ throw err;
4001
+ }
4002
+ }
4003
+ clearCache() {
4004
+ this.cache.clear();
4005
+ }
4006
+ // --- ITextProvider implementation ---
4007
+ resolveModel(tier) {
4008
+ return GROQ_TIERS[tier] ?? this.defaultModel;
4009
+ }
4010
+ async generate(request) {
4011
+ const messages = [];
4012
+ if (request.systemPrompt) {
4013
+ messages.push({ role: "system", content: request.systemPrompt });
4014
+ }
4015
+ for (const m of request.messages) {
4016
+ messages.push({ role: m.role, content: m.content });
4017
+ }
4018
+ const model = request.model ?? (request.tier ? this.resolveModel(request.tier) : this.defaultModel);
4019
+ const groqReq = GroqChatRequestBuilder.for(model).withMessages(messages).withConfig({
4020
+ temperature: request.temperature,
4021
+ maxTokens: request.maxTokens
4022
+ }).build();
4023
+ const resp = await this.httpClient.postChatCompletion(groqReq);
4024
+ const content = resp.choices[0]?.message?.content ?? "";
4025
+ return {
4026
+ text: content,
4027
+ finishReason: resp.choices[0]?.finish_reason ?? "stop",
4028
+ model: resp.model,
4029
+ providerId: this.id,
4030
+ usage: resp.usage ? {
4031
+ inputTokens: resp.usage.prompt_tokens,
4032
+ outputTokens: resp.usage.completion_tokens,
4033
+ totalTokens: resp.usage.total_tokens
4034
+ } : void 0
4035
+ };
4036
+ }
4037
+ async stream(request, callbacks) {
4038
+ const messages = [];
4039
+ if (request.systemPrompt) {
4040
+ messages.push({ role: "system", content: request.systemPrompt });
4041
+ }
4042
+ for (const m of request.messages) {
4043
+ messages.push({ role: m.role, content: m.content });
4044
+ }
4045
+ const model = request.model ?? (request.tier ? this.resolveModel(request.tier) : this.defaultModel);
4046
+ const text = await this.streamChatCompletion(
4047
+ messages,
4048
+ {
4049
+ onChunk: callbacks.onChunk,
4050
+ onError: callbacks.onError
4051
+ },
4052
+ {
4053
+ model,
4054
+ signal: request.signal,
4055
+ generationConfig: {
4056
+ temperature: request.temperature,
4057
+ maxTokens: request.maxTokens
4058
+ }
4059
+ }
4060
+ );
4061
+ const result = {
4062
+ text,
4063
+ finishReason: "stop",
4064
+ model,
4065
+ providerId: this.id
4066
+ };
4067
+ callbacks.onComplete?.(result);
4068
+ return result;
4069
+ }
4070
+ async countTokens(text) {
4071
+ return Math.max(1, Math.ceil(text.length / 4));
4072
+ }
4073
+ async estimateCost(_request) {
4074
+ return 0;
4075
+ }
4076
+ async healthCheck() {
4077
+ try {
4078
+ await this.generateCompletion("ping", {
4079
+ generationConfig: { maxTokens: 1 }
4080
+ });
4081
+ return "healthy";
4082
+ } catch {
4083
+ return "unavailable";
4084
+ }
4085
+ }
4086
+ async executeWithCache(params, factory) {
4087
+ const key = this.cache.generateKey(params);
4088
+ const cached = this.cache.get(key);
4089
+ if (cached !== null) {
4090
+ return cached;
4091
+ }
4092
+ const result = await factory();
4093
+ this.cache.set(key, result);
4094
+ return result;
4095
+ }
4096
+ extractContent(content) {
4097
+ if (!content) {
4098
+ throw new GroqError(
4099
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
4100
+ "No content generated from Groq API"
4101
+ );
4102
+ }
4103
+ return content;
4104
+ }
4105
+ };
4106
+
2968
4107
  // src/core/calculations/random-base36-suffix.ts
2969
4108
  var BASE_36_RADIX = 36;
2970
4109
  var DECIMAL_OFFSET = 2;
@@ -3192,6 +4331,30 @@ function readNumber(source, keys) {
3192
4331
  return null;
3193
4332
  }
3194
4333
 
3195
- export { AIError, AIErrorType, ANTHROPIC_API_VERSION, ANTHROPIC_DEFAULT_MAX_TOKENS, ANTHROPIC_MODELS, ANTHROPIC_TIERS, AnthropicHttpTransport, AnthropicProvider, AnthropicRequestBuilder, BaseProvider, CacheManager, ChatIdGenerator, ChatService, CompositeExecutor, DEFAULT_API_KEY_RULE, DEFAULT_ERROR_CLASSIFICATION_RULE, DEFAULT_HTTP_STATUS_RULE, DEFAULT_PROMPT_STRATEGY, DEFAULT_RETRY_BACKOFF_MULTIPLIER, DEFAULT_RETRY_INITIAL_DELAY_MS, DEFAULT_RETRY_JITTER_FACTOR, DEFAULT_RETRY_MAX_ATTEMPTS, DEFAULT_RETRY_MAX_DELAY_MS, DebounceManager, DedupeExecutor, FalProvider, GEMINI_TIERS, GROQ_MODELS, GROQ_TIERS, GeminiProvider, GroqChatRequestBuilder, GroqHttpClient, GroqHttpTransport, GroqProvider, JSON_CODE_BLOCK_PATTERN, JSON_OBJECT_PATTERN, JsonResponseParser, ProviderError, ProviderFactory, PrunaProvider, QueueExecutor, RequestDeduplicator, RequestQueue, RetryExecutor, RetryManager, SSE_BRIDGE_EVENTS, SSE_DONE_MARKER, SSE_PREFIX, StreamingResponseParser, StructuredPromptBuilder, TEXT_TIERS, ThrottleManager, USER_FRIENDLY_ERROR_MESSAGES, asObjectArray, asRecord, asStringArray, asTrimmedOptionalString, buildCacheKey, buildHttpStatusFallback, cacheManager, chatIdGenerator, clampScore, classifyHttpStatus, consumeAnthropicStream, consumeGroqStream, createAI, createFalProvider, createGeminiProvider, createPrunaProvider, createSseResponse, debounce, debounceManager, defaultEffortForModel, describeAIErrorType, extractContentFromSsePayload, extractErrorMessageFromJsonBody, extractErrorMessageFromResponse, extractJsonArray, extractJsonCandidate, extractJsonFromCodeFence, extractJsonObject, extractSseDataPayload, extractTokenUsage, formatErrorForLogging, generateWithPruna, isAuthErrorType, isMediaProvider, isRetryableErrorType, isSseDataLine, isSseDoneMarker, isTextProvider, isTextTier, isValidApiKey, mapAnthropicStopReason, mergeTierOverrides, messageFormatter, normalizeSseChunk, oneOfCatalog, repairCommonJsonDefects, requestDeduplicator, requestQueue, resolveModel, resolveUserFriendlyErrorMessage, resolveUserFriendlyMessage, retryManager, splitSseLines, throttle, throttleManager, toGroqMessages, validateApiKey, withRetry };
4334
+ // src/providers/groq/groq-container.ts
4335
+ var container = null;
4336
+ function configureProvider(config) {
4337
+ const configStore = new GroqConfigStore();
4338
+ const transport = new GroqHttpTransport(configStore);
4339
+ const httpClient = new GroqHttpClient({ configStore, transport });
4340
+ httpClient.initialize(config);
4341
+ const textGeneration = new TextGenerationService(
4342
+ httpClient,
4343
+ new CacheManager(),
4344
+ config.textModel
4345
+ );
4346
+ const chat = new ChatService(textGeneration);
4347
+ container = { groqHttpClient: httpClient, textGeneration, chat };
4348
+ return container;
4349
+ }
4350
+ function getProvider() {
4351
+ return container;
4352
+ }
4353
+ function resetProvider() {
4354
+ container?.groqHttpClient.reset();
4355
+ container = null;
4356
+ }
4357
+
4358
+ export { AIError, AIErrorType, ANTHROPIC_API_VERSION, ANTHROPIC_DEFAULT_MAX_TOKENS, ANTHROPIC_MODELS, ANTHROPIC_TIERS, API_ENDPOINTS, AnthropicHttpTransport, AnthropicProvider, AnthropicRequestBuilder, AuthenticationError, BaseProvider, CacheManager, CancellationError, ChatIdGenerator, ChatService, CompositeExecutor, DEFAULT_API_KEY_RULE, DEFAULT_BASE_URL, DEFAULT_ERROR_CLASSIFICATION_RULE, DEFAULT_GENERATION_CONFIG, DEFAULT_HTTP_STATUS_RULE, DEFAULT_MODELS, DEFAULT_PROMPT_STRATEGY, DEFAULT_RETRY_BACKOFF_MULTIPLIER, DEFAULT_RETRY_INITIAL_DELAY_MS, DEFAULT_RETRY_JITTER_FACTOR, DEFAULT_RETRY_MAX_ATTEMPTS, DEFAULT_RETRY_MAX_DELAY_MS, DebounceManager, DedupeExecutor, FalProvider, GEMINI_TIERS, GROQ_MODELS, GROQ_TIERS, GeminiProvider, GenerationFailedError, GroqChatRequestBuilder, GroqConfigStore, GroqError, AIErrorType as GroqErrorType, GroqHttpClient, GroqHttpTransport, GroqProvider, JSON_CODE_BLOCK_PATTERN, JSON_OBJECT_PATTERN, JsonResponseParser, ProviderError, ProviderFactory, PrunaProvider, QueueExecutor, RequestDeduplicator, RequestQueue, RetryExecutor, RetryManager, SSE_BRIDGE_EVENTS, SSE_DONE_MARKER, SSE_PREFIX, StreamingResponseParser, StructuredPromptBuilder, TEXT_TIERS, TIMEOUTS, TextGenerationService, ThrottleManager, TimeoutError, USER_FRIENDLY_ERROR_MESSAGES, ValidationError, asObjectArray, asRecord, asStringArray, asTrimmedOptionalString, buildCacheKey, buildHttpStatusFallback, cacheManager, chatIdGenerator, clampScore, classifyHttpStatus, configureProvider, consumeAnthropicStream, consumeGroqStream, createAI, createAssistantMessage, createFalProvider, createGeminiProvider, createMessage, createPrunaProvider, createSseResponse, createSystemMessage, createUserMessage, debounce, debounceManager, defaultEffortForModel, describeAIErrorType, extractContentFromSsePayload, extractErrorMessageFromJsonBody, extractErrorMessageFromResponse, extractJsonArray, extractJsonCandidate, extractJsonFromCodeFence, extractJsonObject, extractSseDataPayload, extractTextFromMessages, extractTokenUsage, formatErrorForLogging, formatMessagesForDisplay, generateImageThenVideo, generate as generatePruna, generateWithPruna, getProvider, isAuthErrorType, isMediaProvider, isRetryableErrorType, isSseDataLine, isSseDoneMarker, isTextProvider, isTextTier, isValidApiKey, mapAnthropicStopReason, mergeTierOverrides, messageFormatter, normalizeSseChunk, oneOfCatalog, pollForResult, promptToMessages, repairCommonJsonDefects, requestDeduplicator, requestQueue, requestStructuredJson, resetProvider, resolveModel, resolveUserFriendlyErrorMessage, resolveUserFriendlyMessage, retryManager, splitSseLines, submitPrediction, throttle, throttleManager, toGroqMessages, uploadImage, validateApiKey, withRetry };
3196
4359
  //# sourceMappingURL=index.js.map
3197
4360
  //# sourceMappingURL=index.js.map