getaiapi 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/dist/chunk-O4TLMX4T.js +1363 -0
- package/dist/chunk-O4TLMX4T.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +211 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +123 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
- package/registry/.gitkeep +0 -0
- package/registry/catalog.json +4106 -0
- package/registry/categories.json +18 -0
- package/registry/registry.json +57243 -0
|
@@ -0,0 +1,1363 @@
|
|
|
1
|
+
// src/gateway.ts
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
|
|
4
|
+
// src/errors.ts
|
|
5
|
+
var GetAIApiError = class extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "GetAIApiError";
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var AuthError = class extends GetAIApiError {
|
|
12
|
+
provider;
|
|
13
|
+
envVar;
|
|
14
|
+
constructor(provider, envVar) {
|
|
15
|
+
super(`Missing or invalid API key for ${provider}. Set the ${envVar} environment variable.`);
|
|
16
|
+
this.name = "AuthError";
|
|
17
|
+
this.provider = provider;
|
|
18
|
+
this.envVar = envVar;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
var ModelNotFoundError = class extends GetAIApiError {
|
|
22
|
+
query;
|
|
23
|
+
suggestions;
|
|
24
|
+
constructor(query, suggestions = []) {
|
|
25
|
+
const hint = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?` : "";
|
|
26
|
+
super(`Model "${query}" not found.${hint}`);
|
|
27
|
+
this.name = "ModelNotFoundError";
|
|
28
|
+
this.query = query;
|
|
29
|
+
this.suggestions = suggestions;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var ValidationError = class extends GetAIApiError {
|
|
33
|
+
field;
|
|
34
|
+
constructor(field, message) {
|
|
35
|
+
super(`Validation error on "${field}": ${message}`);
|
|
36
|
+
this.name = "ValidationError";
|
|
37
|
+
this.field = field;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var ProviderError = class extends GetAIApiError {
|
|
41
|
+
provider;
|
|
42
|
+
model;
|
|
43
|
+
statusCode;
|
|
44
|
+
raw;
|
|
45
|
+
constructor(provider, model, statusCode, raw) {
|
|
46
|
+
super(
|
|
47
|
+
`Provider ${provider} returned status ${statusCode} for model "${model}".`
|
|
48
|
+
);
|
|
49
|
+
this.name = "ProviderError";
|
|
50
|
+
this.provider = provider;
|
|
51
|
+
this.model = model;
|
|
52
|
+
this.statusCode = statusCode;
|
|
53
|
+
this.raw = raw;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var TimeoutError = class extends GetAIApiError {
|
|
57
|
+
provider;
|
|
58
|
+
model;
|
|
59
|
+
timeoutMs;
|
|
60
|
+
constructor(provider, model, timeoutMs) {
|
|
61
|
+
super(
|
|
62
|
+
`Generation timed out after ${timeoutMs}ms for model "${model}" on ${provider}.`
|
|
63
|
+
);
|
|
64
|
+
this.name = "TimeoutError";
|
|
65
|
+
this.provider = provider;
|
|
66
|
+
this.model = model;
|
|
67
|
+
this.timeoutMs = timeoutMs;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var RateLimitError = class extends GetAIApiError {
|
|
71
|
+
provider;
|
|
72
|
+
retryAfterMs;
|
|
73
|
+
constructor(provider, retryAfterMs) {
|
|
74
|
+
super(
|
|
75
|
+
`Rate limited by ${provider}. Retry after ${retryAfterMs}ms.`
|
|
76
|
+
);
|
|
77
|
+
this.name = "RateLimitError";
|
|
78
|
+
this.provider = provider;
|
|
79
|
+
this.retryAfterMs = retryAfterMs;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// src/auth.ts
|
|
84
|
+
var ENV_MAP = {
|
|
85
|
+
"fal-ai": "FAL_KEY",
|
|
86
|
+
replicate: "REPLICATE_API_TOKEN",
|
|
87
|
+
wavespeed: "WAVESPEED_API_KEY"
|
|
88
|
+
};
|
|
89
|
+
var AuthManager = class {
|
|
90
|
+
keys;
|
|
91
|
+
constructor() {
|
|
92
|
+
this.keys = /* @__PURE__ */ new Map();
|
|
93
|
+
for (const [provider, envVar] of Object.entries(ENV_MAP)) {
|
|
94
|
+
const key = process.env[envVar]?.trim();
|
|
95
|
+
if (key) this.keys.set(provider, key);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
availableProviders() {
|
|
99
|
+
return [...this.keys.keys()];
|
|
100
|
+
}
|
|
101
|
+
getKey(provider) {
|
|
102
|
+
const key = this.keys.get(provider);
|
|
103
|
+
if (!key) {
|
|
104
|
+
throw new AuthError(provider, ENV_MAP[provider]);
|
|
105
|
+
}
|
|
106
|
+
return key;
|
|
107
|
+
}
|
|
108
|
+
canAccess(model) {
|
|
109
|
+
return model.providers.some((p) => this.keys.has(p.provider));
|
|
110
|
+
}
|
|
111
|
+
listAvailableModels(registry) {
|
|
112
|
+
return registry.filter((m) => this.canAccess(m));
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// src/resolver.ts
|
|
117
|
+
import { readFileSync } from "fs";
|
|
118
|
+
import { resolve, dirname } from "path";
|
|
119
|
+
import { fileURLToPath } from "url";
|
|
120
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
121
|
+
var __dirname = dirname(__filename);
|
|
122
|
+
var registryCache = null;
|
|
123
|
+
function loadRegistry() {
|
|
124
|
+
if (registryCache) {
|
|
125
|
+
return registryCache;
|
|
126
|
+
}
|
|
127
|
+
let dir = __dirname;
|
|
128
|
+
for (let i = 0; i < 5; i++) {
|
|
129
|
+
const candidate = resolve(dir, "registry", "registry.json");
|
|
130
|
+
try {
|
|
131
|
+
const raw = readFileSync(candidate, "utf-8");
|
|
132
|
+
registryCache = JSON.parse(raw);
|
|
133
|
+
return registryCache;
|
|
134
|
+
} catch {
|
|
135
|
+
dir = dirname(dir);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
throw new Error(
|
|
139
|
+
"Could not find registry/registry.json. Searched upward from: " + __dirname
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
function normalizeModelName(input) {
|
|
143
|
+
return input.toLowerCase().replace(/[^a-z0-9]/g, "").replace(/(?<=\d)v(?=\d)/g, "").replace(/v(?=\d)/g, "");
|
|
144
|
+
}
|
|
145
|
+
function resolveModel(query, availableProviders) {
|
|
146
|
+
if (!query || typeof query !== "string" || query.trim() === "") {
|
|
147
|
+
throw new ModelNotFoundError("Model name is required");
|
|
148
|
+
}
|
|
149
|
+
const trimmedQuery = query.trim();
|
|
150
|
+
const registry = loadRegistry();
|
|
151
|
+
let matched;
|
|
152
|
+
matched = registry.find((e) => e.canonical_name === trimmedQuery);
|
|
153
|
+
if (!matched) {
|
|
154
|
+
matched = registry.find(
|
|
155
|
+
(e) => e.aliases.some((a) => a === trimmedQuery)
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (!matched) {
|
|
159
|
+
const normalizedQuery = normalizeModelName(trimmedQuery);
|
|
160
|
+
matched = registry.find(
|
|
161
|
+
(e) => normalizeModelName(e.canonical_name) === normalizedQuery
|
|
162
|
+
);
|
|
163
|
+
if (!matched) {
|
|
164
|
+
matched = registry.find(
|
|
165
|
+
(e) => e.aliases.some((a) => normalizeModelName(a) === normalizedQuery)
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (!matched) {
|
|
170
|
+
const suggestions = findSuggestions(trimmedQuery, registry);
|
|
171
|
+
throw new ModelNotFoundError(trimmedQuery, suggestions);
|
|
172
|
+
}
|
|
173
|
+
if (availableProviders && availableProviders.length > 0) {
|
|
174
|
+
const filteredProviders = matched.providers.filter(
|
|
175
|
+
(p) => availableProviders.includes(p.provider)
|
|
176
|
+
);
|
|
177
|
+
if (filteredProviders.length === 0) {
|
|
178
|
+
const suggestions = findSuggestions(trimmedQuery, registry);
|
|
179
|
+
throw new ModelNotFoundError(trimmedQuery, suggestions);
|
|
180
|
+
}
|
|
181
|
+
return { ...matched, providers: filteredProviders };
|
|
182
|
+
}
|
|
183
|
+
return matched;
|
|
184
|
+
}
|
|
185
|
+
function findSuggestions(query, registry) {
|
|
186
|
+
const normalizedQuery = normalizeModelName(query);
|
|
187
|
+
if (normalizedQuery === "") {
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
const matches = registry.filter((e) => {
|
|
191
|
+
const normalizedCanonical = normalizeModelName(e.canonical_name);
|
|
192
|
+
if (normalizedCanonical.startsWith(normalizedQuery) || normalizedCanonical.includes(normalizedQuery) || normalizedQuery.startsWith(normalizedCanonical)) {
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
const minLen = Math.min(normalizedQuery.length, normalizedCanonical.length);
|
|
196
|
+
let shared = 0;
|
|
197
|
+
for (let i = 0; i < minLen; i++) {
|
|
198
|
+
if (normalizedQuery[i] === normalizedCanonical[i]) {
|
|
199
|
+
shared++;
|
|
200
|
+
} else {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return shared >= 3 && shared >= normalizedQuery.length * 0.3;
|
|
205
|
+
});
|
|
206
|
+
return matches.sort((a, b) => a.canonical_name.length - b.canonical_name.length).slice(0, 5).map((e) => e.canonical_name);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/mapper.ts
|
|
210
|
+
function mapInput(request, binding, template) {
|
|
211
|
+
const result = {};
|
|
212
|
+
const provider = binding.provider;
|
|
213
|
+
for (const mapping of template.input_mappings) {
|
|
214
|
+
const value = getUniversalValue(request, mapping.universal);
|
|
215
|
+
if (value === void 0 || value === null) {
|
|
216
|
+
if (mapping.required) {
|
|
217
|
+
throw new ValidationError(
|
|
218
|
+
mapping.universal,
|
|
219
|
+
`"${mapping.universal}" is required but was not provided.`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const providerKey = mapping.providers[provider];
|
|
225
|
+
if (providerKey === void 0) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const transformed = applyTransform(value, mapping, provider);
|
|
229
|
+
if (Array.isArray(providerKey)) {
|
|
230
|
+
if (typeof transformed === "object" && transformed !== null && !Array.isArray(transformed)) {
|
|
231
|
+
const obj = transformed;
|
|
232
|
+
for (const key of providerKey) {
|
|
233
|
+
if (obj[key] !== void 0) {
|
|
234
|
+
result[key] = obj[key];
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
result[providerKey] = transformed;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (request.options) {
|
|
243
|
+
for (const [key, val] of Object.entries(request.options)) {
|
|
244
|
+
result[key] = val;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
function getUniversalValue(request, field) {
|
|
250
|
+
return request[field];
|
|
251
|
+
}
|
|
252
|
+
function applyTransform(value, mapping, provider) {
|
|
253
|
+
const transform = mapping.transform;
|
|
254
|
+
if (!transform || transform === "none") {
|
|
255
|
+
return value;
|
|
256
|
+
}
|
|
257
|
+
if (transform === "flip_boolean") {
|
|
258
|
+
if (provider === "replicate") {
|
|
259
|
+
return !value;
|
|
260
|
+
}
|
|
261
|
+
return value;
|
|
262
|
+
}
|
|
263
|
+
if (transform === "parse_size") {
|
|
264
|
+
return parseSizeForProvider(value, mapping, provider);
|
|
265
|
+
}
|
|
266
|
+
return value;
|
|
267
|
+
}
|
|
268
|
+
function parseSizeForProvider(value, mapping, provider) {
|
|
269
|
+
if (provider === "fal-ai") {
|
|
270
|
+
if (typeof value === "string") {
|
|
271
|
+
const [w, h] = value.split("x").map(Number);
|
|
272
|
+
return { width: w, height: h };
|
|
273
|
+
}
|
|
274
|
+
return value;
|
|
275
|
+
}
|
|
276
|
+
if (provider === "replicate") {
|
|
277
|
+
if (typeof value === "string") {
|
|
278
|
+
const [w, h] = value.split("x").map(Number);
|
|
279
|
+
return { width: w, height: h };
|
|
280
|
+
}
|
|
281
|
+
if (typeof value === "object" && value !== null) {
|
|
282
|
+
return value;
|
|
283
|
+
}
|
|
284
|
+
return value;
|
|
285
|
+
}
|
|
286
|
+
return value;
|
|
287
|
+
}
|
|
288
|
+
function mapOutput(raw, outputMapping) {
|
|
289
|
+
const { type, extract_path, content_type } = outputMapping;
|
|
290
|
+
const defaultContentType = content_type || "image/jpeg";
|
|
291
|
+
const data = raw;
|
|
292
|
+
if (extract_path === "images[].url") {
|
|
293
|
+
const images = data?.images ?? [];
|
|
294
|
+
return images.map((img) => ({
|
|
295
|
+
type,
|
|
296
|
+
url: img.url,
|
|
297
|
+
content_type: img.content_type || defaultContentType
|
|
298
|
+
}));
|
|
299
|
+
}
|
|
300
|
+
if (extract_path === "output[]") {
|
|
301
|
+
const arr = Array.isArray(data) ? data : data?.output ?? [];
|
|
302
|
+
return arr.map((url) => ({
|
|
303
|
+
type,
|
|
304
|
+
url,
|
|
305
|
+
content_type: defaultContentType
|
|
306
|
+
}));
|
|
307
|
+
}
|
|
308
|
+
if (extract_path === "data.outputs[]") {
|
|
309
|
+
const outputs = data?.data?.outputs ?? [];
|
|
310
|
+
return outputs.map((url) => ({
|
|
311
|
+
type,
|
|
312
|
+
url,
|
|
313
|
+
content_type: defaultContentType
|
|
314
|
+
}));
|
|
315
|
+
}
|
|
316
|
+
if (extract_path === "video.url") {
|
|
317
|
+
return [{
|
|
318
|
+
type: "video",
|
|
319
|
+
url: data?.video?.url,
|
|
320
|
+
content_type: "video/mp4"
|
|
321
|
+
}];
|
|
322
|
+
}
|
|
323
|
+
if (extract_path === "audio.url") {
|
|
324
|
+
return [{
|
|
325
|
+
type: "audio",
|
|
326
|
+
url: data?.audio?.url,
|
|
327
|
+
content_type: "audio/mpeg"
|
|
328
|
+
}];
|
|
329
|
+
}
|
|
330
|
+
return genericExtract(data, extract_path, type, defaultContentType);
|
|
331
|
+
}
|
|
332
|
+
function genericExtract(data, path, type, contentType) {
|
|
333
|
+
const segments = path.split(".");
|
|
334
|
+
let current = data;
|
|
335
|
+
for (const seg of segments) {
|
|
336
|
+
if (current === null || current === void 0) return [];
|
|
337
|
+
const arrayMatch = seg.match(/^(.+)\[\]$/);
|
|
338
|
+
if (arrayMatch) {
|
|
339
|
+
const key = arrayMatch[1];
|
|
340
|
+
current = current[key];
|
|
341
|
+
if (Array.isArray(current)) {
|
|
342
|
+
const remaining = segments.slice(segments.indexOf(seg) + 1).join(".");
|
|
343
|
+
if (remaining) {
|
|
344
|
+
return current.map((item) => ({
|
|
345
|
+
type,
|
|
346
|
+
url: getNestedValue(item, remaining),
|
|
347
|
+
content_type: contentType
|
|
348
|
+
}));
|
|
349
|
+
}
|
|
350
|
+
return current.map((item) => ({
|
|
351
|
+
type,
|
|
352
|
+
url: typeof item === "string" ? item : item?.url,
|
|
353
|
+
content_type: contentType
|
|
354
|
+
}));
|
|
355
|
+
}
|
|
356
|
+
return [];
|
|
357
|
+
}
|
|
358
|
+
current = current[seg];
|
|
359
|
+
}
|
|
360
|
+
if (typeof current === "string") {
|
|
361
|
+
return [{ type, url: current, content_type: contentType }];
|
|
362
|
+
}
|
|
363
|
+
return [];
|
|
364
|
+
}
|
|
365
|
+
function getNestedValue(obj, path) {
|
|
366
|
+
let current = obj;
|
|
367
|
+
for (const key of path.split(".")) {
|
|
368
|
+
if (current === null || current === void 0) return void 0;
|
|
369
|
+
current = current[key];
|
|
370
|
+
}
|
|
371
|
+
return current;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/categories/text-to-image.ts
|
|
375
|
+
var textToImageTemplate = {
|
|
376
|
+
category: "text-to-image",
|
|
377
|
+
input_mappings: [
|
|
378
|
+
{
|
|
379
|
+
universal: "prompt",
|
|
380
|
+
providers: {
|
|
381
|
+
"fal-ai": "prompt",
|
|
382
|
+
"replicate": "prompt",
|
|
383
|
+
"wavespeed": "prompt"
|
|
384
|
+
},
|
|
385
|
+
required: true
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
universal: "negative_prompt",
|
|
389
|
+
providers: {
|
|
390
|
+
"fal-ai": "negative_prompt",
|
|
391
|
+
"replicate": "negative_prompt",
|
|
392
|
+
"wavespeed": "negative_prompt"
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
universal: "count",
|
|
397
|
+
providers: {
|
|
398
|
+
"fal-ai": "num_images",
|
|
399
|
+
"replicate": "num_outputs",
|
|
400
|
+
"wavespeed": "num_outputs"
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
universal: "size",
|
|
405
|
+
providers: {
|
|
406
|
+
"fal-ai": "image_size",
|
|
407
|
+
"replicate": ["width", "height"],
|
|
408
|
+
"wavespeed": "resolution"
|
|
409
|
+
},
|
|
410
|
+
transform: "parse_size"
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
universal: "guidance",
|
|
414
|
+
providers: {
|
|
415
|
+
"fal-ai": "guidance_scale",
|
|
416
|
+
"replicate": "guidance",
|
|
417
|
+
"wavespeed": "guidance_scale"
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
universal: "steps",
|
|
422
|
+
providers: {
|
|
423
|
+
"fal-ai": "num_inference_steps",
|
|
424
|
+
"replicate": "num_inference_steps",
|
|
425
|
+
"wavespeed": "num_inference_steps"
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
universal: "seed",
|
|
430
|
+
providers: {
|
|
431
|
+
"fal-ai": "seed",
|
|
432
|
+
"replicate": "seed",
|
|
433
|
+
"wavespeed": "seed"
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
universal: "format",
|
|
438
|
+
providers: {
|
|
439
|
+
"fal-ai": "output_format",
|
|
440
|
+
"replicate": "output_format",
|
|
441
|
+
"wavespeed": "output_format"
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
universal: "quality",
|
|
446
|
+
providers: {
|
|
447
|
+
"fal-ai": "quality",
|
|
448
|
+
"replicate": "output_quality",
|
|
449
|
+
"wavespeed": "quality"
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
universal: "safety",
|
|
454
|
+
providers: {
|
|
455
|
+
"fal-ai": "enable_safety_checker",
|
|
456
|
+
"replicate": "disable_safety_checker",
|
|
457
|
+
"wavespeed": "enable_safety_checker"
|
|
458
|
+
},
|
|
459
|
+
transform: "flip_boolean"
|
|
460
|
+
}
|
|
461
|
+
],
|
|
462
|
+
output_type: "image",
|
|
463
|
+
output_extract: {
|
|
464
|
+
"fal-ai": "images[].url",
|
|
465
|
+
"replicate": "output[]",
|
|
466
|
+
"wavespeed": "data.outputs[]"
|
|
467
|
+
},
|
|
468
|
+
default_timeout_ms: 6e4
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
// src/categories/image-edit.ts
|
|
472
|
+
var imageEditTemplate = {
|
|
473
|
+
category: "image-edit",
|
|
474
|
+
input_mappings: [
|
|
475
|
+
{
|
|
476
|
+
universal: "image",
|
|
477
|
+
providers: {
|
|
478
|
+
"fal-ai": "image_url",
|
|
479
|
+
"replicate": "image",
|
|
480
|
+
"wavespeed": "image_url"
|
|
481
|
+
},
|
|
482
|
+
required: true
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
universal: "prompt",
|
|
486
|
+
providers: {
|
|
487
|
+
"fal-ai": "prompt",
|
|
488
|
+
"replicate": "prompt",
|
|
489
|
+
"wavespeed": "prompt"
|
|
490
|
+
},
|
|
491
|
+
required: true
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
universal: "strength",
|
|
495
|
+
providers: {
|
|
496
|
+
"fal-ai": "strength",
|
|
497
|
+
"replicate": "strength",
|
|
498
|
+
"wavespeed": "strength"
|
|
499
|
+
}
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
universal: "mask",
|
|
503
|
+
providers: {
|
|
504
|
+
"fal-ai": "mask_url",
|
|
505
|
+
"replicate": "mask",
|
|
506
|
+
"wavespeed": "mask_url"
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
universal: "seed",
|
|
511
|
+
providers: {
|
|
512
|
+
"fal-ai": "seed",
|
|
513
|
+
"replicate": "seed",
|
|
514
|
+
"wavespeed": "seed"
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
universal: "format",
|
|
519
|
+
providers: {
|
|
520
|
+
"fal-ai": "output_format",
|
|
521
|
+
"replicate": "output_format",
|
|
522
|
+
"wavespeed": "output_format"
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
universal: "quality",
|
|
527
|
+
providers: {
|
|
528
|
+
"fal-ai": "quality",
|
|
529
|
+
"replicate": "output_quality",
|
|
530
|
+
"wavespeed": "quality"
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
universal: "safety",
|
|
535
|
+
providers: {
|
|
536
|
+
"fal-ai": "enable_safety_checker",
|
|
537
|
+
"replicate": "disable_safety_checker",
|
|
538
|
+
"wavespeed": "enable_safety_checker"
|
|
539
|
+
},
|
|
540
|
+
transform: "flip_boolean"
|
|
541
|
+
}
|
|
542
|
+
],
|
|
543
|
+
output_type: "image",
|
|
544
|
+
output_extract: {
|
|
545
|
+
"fal-ai": "images[].url",
|
|
546
|
+
"replicate": "output[]",
|
|
547
|
+
"wavespeed": "data.outputs[]"
|
|
548
|
+
},
|
|
549
|
+
default_timeout_ms: 6e4
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
// src/categories/text-to-video.ts
|
|
553
|
+
var textToVideoTemplate = {
|
|
554
|
+
category: "text-to-video",
|
|
555
|
+
input_mappings: [
|
|
556
|
+
{
|
|
557
|
+
universal: "prompt",
|
|
558
|
+
providers: {
|
|
559
|
+
"fal-ai": "prompt",
|
|
560
|
+
"replicate": "prompt",
|
|
561
|
+
"wavespeed": "prompt"
|
|
562
|
+
},
|
|
563
|
+
required: true
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
universal: "negative_prompt",
|
|
567
|
+
providers: {
|
|
568
|
+
"fal-ai": "negative_prompt",
|
|
569
|
+
"replicate": "negative_prompt",
|
|
570
|
+
"wavespeed": "negative_prompt"
|
|
571
|
+
}
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
universal: "count",
|
|
575
|
+
providers: {
|
|
576
|
+
"fal-ai": "num_videos",
|
|
577
|
+
"replicate": "num_outputs",
|
|
578
|
+
"wavespeed": "num_outputs"
|
|
579
|
+
}
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
universal: "size",
|
|
583
|
+
providers: {
|
|
584
|
+
"fal-ai": "video_size",
|
|
585
|
+
"replicate": ["width", "height"],
|
|
586
|
+
"wavespeed": "resolution"
|
|
587
|
+
},
|
|
588
|
+
transform: "parse_size"
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
universal: "guidance",
|
|
592
|
+
providers: {
|
|
593
|
+
"fal-ai": "guidance_scale",
|
|
594
|
+
"replicate": "guidance",
|
|
595
|
+
"wavespeed": "guidance_scale"
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
universal: "steps",
|
|
600
|
+
providers: {
|
|
601
|
+
"fal-ai": "num_inference_steps",
|
|
602
|
+
"replicate": "num_inference_steps",
|
|
603
|
+
"wavespeed": "num_inference_steps"
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
universal: "seed",
|
|
608
|
+
providers: {
|
|
609
|
+
"fal-ai": "seed",
|
|
610
|
+
"replicate": "seed",
|
|
611
|
+
"wavespeed": "seed"
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
universal: "format",
|
|
616
|
+
providers: {
|
|
617
|
+
"fal-ai": "output_format",
|
|
618
|
+
"replicate": "output_format",
|
|
619
|
+
"wavespeed": "output_format"
|
|
620
|
+
}
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
universal: "safety",
|
|
624
|
+
providers: {
|
|
625
|
+
"fal-ai": "enable_safety_checker",
|
|
626
|
+
"replicate": "disable_safety_checker",
|
|
627
|
+
"wavespeed": "enable_safety_checker"
|
|
628
|
+
},
|
|
629
|
+
transform: "flip_boolean"
|
|
630
|
+
}
|
|
631
|
+
],
|
|
632
|
+
output_type: "video",
|
|
633
|
+
output_extract: {
|
|
634
|
+
"fal-ai": "video.url",
|
|
635
|
+
"replicate": "output",
|
|
636
|
+
"wavespeed": "data.outputs[]"
|
|
637
|
+
},
|
|
638
|
+
default_timeout_ms: 3e5
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
// src/categories/image-to-video.ts
|
|
642
|
+
var imageToVideoTemplate = {
|
|
643
|
+
category: "image-to-video",
|
|
644
|
+
input_mappings: [
|
|
645
|
+
{
|
|
646
|
+
universal: "image",
|
|
647
|
+
providers: {
|
|
648
|
+
"fal-ai": "image_url",
|
|
649
|
+
"replicate": "image",
|
|
650
|
+
"wavespeed": "image_url"
|
|
651
|
+
},
|
|
652
|
+
required: true
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
universal: "prompt",
|
|
656
|
+
providers: {
|
|
657
|
+
"fal-ai": "prompt",
|
|
658
|
+
"replicate": "prompt",
|
|
659
|
+
"wavespeed": "prompt"
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
universal: "negative_prompt",
|
|
664
|
+
providers: {
|
|
665
|
+
"fal-ai": "negative_prompt",
|
|
666
|
+
"replicate": "negative_prompt",
|
|
667
|
+
"wavespeed": "negative_prompt"
|
|
668
|
+
}
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
universal: "seed",
|
|
672
|
+
providers: {
|
|
673
|
+
"fal-ai": "seed",
|
|
674
|
+
"replicate": "seed",
|
|
675
|
+
"wavespeed": "seed"
|
|
676
|
+
}
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
universal: "guidance",
|
|
680
|
+
providers: {
|
|
681
|
+
"fal-ai": "guidance_scale",
|
|
682
|
+
"replicate": "guidance",
|
|
683
|
+
"wavespeed": "guidance_scale"
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
universal: "steps",
|
|
688
|
+
providers: {
|
|
689
|
+
"fal-ai": "num_inference_steps",
|
|
690
|
+
"replicate": "num_inference_steps",
|
|
691
|
+
"wavespeed": "num_inference_steps"
|
|
692
|
+
}
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
universal: "format",
|
|
696
|
+
providers: {
|
|
697
|
+
"fal-ai": "output_format",
|
|
698
|
+
"replicate": "output_format",
|
|
699
|
+
"wavespeed": "output_format"
|
|
700
|
+
}
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
universal: "safety",
|
|
704
|
+
providers: {
|
|
705
|
+
"fal-ai": "enable_safety_checker",
|
|
706
|
+
"replicate": "disable_safety_checker",
|
|
707
|
+
"wavespeed": "enable_safety_checker"
|
|
708
|
+
},
|
|
709
|
+
transform: "flip_boolean"
|
|
710
|
+
}
|
|
711
|
+
],
|
|
712
|
+
output_type: "video",
|
|
713
|
+
output_extract: {
|
|
714
|
+
"fal-ai": "video.url",
|
|
715
|
+
"replicate": "output",
|
|
716
|
+
"wavespeed": "data.outputs[]"
|
|
717
|
+
},
|
|
718
|
+
default_timeout_ms: 3e5
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
// src/categories/upscale-image.ts
|
|
722
|
+
var upscaleImageTemplate = {
|
|
723
|
+
category: "upscale-image",
|
|
724
|
+
input_mappings: [
|
|
725
|
+
{
|
|
726
|
+
universal: "image",
|
|
727
|
+
providers: {
|
|
728
|
+
"fal-ai": "image_url",
|
|
729
|
+
"replicate": "image",
|
|
730
|
+
"wavespeed": "image_url"
|
|
731
|
+
},
|
|
732
|
+
required: true
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
universal: "strength",
|
|
736
|
+
providers: {
|
|
737
|
+
"fal-ai": "scale",
|
|
738
|
+
"replicate": "scale",
|
|
739
|
+
"wavespeed": "scale"
|
|
740
|
+
}
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
universal: "format",
|
|
744
|
+
providers: {
|
|
745
|
+
"fal-ai": "output_format",
|
|
746
|
+
"replicate": "output_format",
|
|
747
|
+
"wavespeed": "output_format"
|
|
748
|
+
}
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
universal: "quality",
|
|
752
|
+
providers: {
|
|
753
|
+
"fal-ai": "quality",
|
|
754
|
+
"replicate": "output_quality",
|
|
755
|
+
"wavespeed": "quality"
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
],
|
|
759
|
+
output_type: "image",
|
|
760
|
+
output_extract: {
|
|
761
|
+
"fal-ai": "images[].url",
|
|
762
|
+
"replicate": "output[]",
|
|
763
|
+
"wavespeed": "data.outputs[]"
|
|
764
|
+
},
|
|
765
|
+
default_timeout_ms: 12e4
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
// src/categories/text-to-audio.ts
|
|
769
|
+
var textToAudioTemplate = {
|
|
770
|
+
category: "text-to-audio",
|
|
771
|
+
input_mappings: [
|
|
772
|
+
{
|
|
773
|
+
universal: "prompt",
|
|
774
|
+
providers: {
|
|
775
|
+
"fal-ai": "text",
|
|
776
|
+
"replicate": "prompt",
|
|
777
|
+
"wavespeed": "prompt"
|
|
778
|
+
},
|
|
779
|
+
required: true
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
universal: "count",
|
|
783
|
+
providers: {
|
|
784
|
+
"fal-ai": "num_outputs",
|
|
785
|
+
"replicate": "num_outputs",
|
|
786
|
+
"wavespeed": "num_outputs"
|
|
787
|
+
}
|
|
788
|
+
},
|
|
789
|
+
{
|
|
790
|
+
universal: "format",
|
|
791
|
+
providers: {
|
|
792
|
+
"fal-ai": "output_format",
|
|
793
|
+
"replicate": "output_format",
|
|
794
|
+
"wavespeed": "output_format"
|
|
795
|
+
}
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
universal: "seed",
|
|
799
|
+
providers: {
|
|
800
|
+
"fal-ai": "seed",
|
|
801
|
+
"replicate": "seed",
|
|
802
|
+
"wavespeed": "seed"
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
],
|
|
806
|
+
output_type: "audio",
|
|
807
|
+
output_extract: {
|
|
808
|
+
"fal-ai": "audio.url",
|
|
809
|
+
"replicate": "output",
|
|
810
|
+
"wavespeed": "data.outputs[]"
|
|
811
|
+
},
|
|
812
|
+
default_timeout_ms: 6e4
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
// src/categories/audio-to-text.ts
|
|
816
|
+
var audioToTextTemplate = {
|
|
817
|
+
category: "audio-to-text",
|
|
818
|
+
input_mappings: [
|
|
819
|
+
{
|
|
820
|
+
universal: "audio",
|
|
821
|
+
providers: {
|
|
822
|
+
"fal-ai": "audio_url",
|
|
823
|
+
"replicate": "audio",
|
|
824
|
+
"wavespeed": "audio_url"
|
|
825
|
+
},
|
|
826
|
+
required: true
|
|
827
|
+
}
|
|
828
|
+
],
|
|
829
|
+
output_type: "text",
|
|
830
|
+
output_extract: {
|
|
831
|
+
"fal-ai": "text",
|
|
832
|
+
"replicate": "output.text",
|
|
833
|
+
"wavespeed": "data.outputs[]"
|
|
834
|
+
},
|
|
835
|
+
default_timeout_ms: 12e4
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
// src/categories/remove-background.ts
|
|
839
|
+
var removeBackgroundTemplate = {
|
|
840
|
+
category: "remove-background",
|
|
841
|
+
input_mappings: [
|
|
842
|
+
{
|
|
843
|
+
universal: "image",
|
|
844
|
+
providers: {
|
|
845
|
+
"fal-ai": "image_url",
|
|
846
|
+
"replicate": "image",
|
|
847
|
+
"wavespeed": "image_url"
|
|
848
|
+
},
|
|
849
|
+
required: true
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
universal: "format",
|
|
853
|
+
providers: {
|
|
854
|
+
"fal-ai": "output_format",
|
|
855
|
+
"replicate": "output_format",
|
|
856
|
+
"wavespeed": "output_format"
|
|
857
|
+
}
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
universal: "quality",
|
|
861
|
+
providers: {
|
|
862
|
+
"fal-ai": "quality",
|
|
863
|
+
"replicate": "output_quality",
|
|
864
|
+
"wavespeed": "quality"
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
],
|
|
868
|
+
output_type: "image",
|
|
869
|
+
output_extract: {
|
|
870
|
+
"fal-ai": "images[].url",
|
|
871
|
+
"replicate": "output[]",
|
|
872
|
+
"wavespeed": "data.outputs[]"
|
|
873
|
+
},
|
|
874
|
+
default_timeout_ms: 6e4
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
// src/categories/index.ts
|
|
878
|
+
var templates = {
|
|
879
|
+
"text-to-image": textToImageTemplate,
|
|
880
|
+
"image-edit": imageEditTemplate,
|
|
881
|
+
"text-to-video": textToVideoTemplate,
|
|
882
|
+
"image-to-video": imageToVideoTemplate,
|
|
883
|
+
"upscale-image": upscaleImageTemplate,
|
|
884
|
+
"text-to-audio": textToAudioTemplate,
|
|
885
|
+
"audio-to-text": audioToTextTemplate,
|
|
886
|
+
"remove-background": removeBackgroundTemplate
|
|
887
|
+
};
|
|
888
|
+
function getCategoryTemplate(category) {
|
|
889
|
+
return templates[category];
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// src/adapters/fal-ai.ts
|
|
893
|
+
var BASE_URL = "https://queue.fal.run";
|
|
894
|
+
async function handleHttpErrors(response, endpoint) {
|
|
895
|
+
if (response.ok) return;
|
|
896
|
+
const status = response.status;
|
|
897
|
+
if (status === 401) {
|
|
898
|
+
throw new AuthError("fal-ai", "FAL_KEY");
|
|
899
|
+
}
|
|
900
|
+
if (status === 429) {
|
|
901
|
+
const retryAfter = response.headers.get("retry-after");
|
|
902
|
+
const retryMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : 6e4;
|
|
903
|
+
throw new RateLimitError("fal-ai", retryMs);
|
|
904
|
+
}
|
|
905
|
+
let raw;
|
|
906
|
+
try {
|
|
907
|
+
raw = await response.json();
|
|
908
|
+
} catch {
|
|
909
|
+
raw = await response.text().catch(() => null);
|
|
910
|
+
}
|
|
911
|
+
throw new ProviderError("fal-ai", endpoint, status, raw);
|
|
912
|
+
}
|
|
913
|
+
function authHeaders(auth) {
|
|
914
|
+
return {
|
|
915
|
+
Authorization: `Key ${auth}`,
|
|
916
|
+
"Content-Type": "application/json"
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
var falAiAdapter = {
|
|
920
|
+
name: "fal-ai",
|
|
921
|
+
async submit(endpoint, params, auth) {
|
|
922
|
+
const url = `${BASE_URL}/${endpoint}`;
|
|
923
|
+
const response = await fetch(url, {
|
|
924
|
+
method: "POST",
|
|
925
|
+
headers: authHeaders(auth),
|
|
926
|
+
body: JSON.stringify(params)
|
|
927
|
+
});
|
|
928
|
+
await handleHttpErrors(response, endpoint);
|
|
929
|
+
const data = await response.json();
|
|
930
|
+
return {
|
|
931
|
+
id: data.request_id,
|
|
932
|
+
status: "pending"
|
|
933
|
+
};
|
|
934
|
+
},
|
|
935
|
+
async poll(taskId, auth, endpoint) {
|
|
936
|
+
if (!endpoint) {
|
|
937
|
+
throw new ProviderError("fal-ai", "unknown", 400, "endpoint is required for polling");
|
|
938
|
+
}
|
|
939
|
+
const statusUrl = `${BASE_URL}/${endpoint}/requests/${taskId}/status`;
|
|
940
|
+
const statusResponse = await fetch(statusUrl, {
|
|
941
|
+
headers: { Authorization: `Key ${auth}` }
|
|
942
|
+
});
|
|
943
|
+
await handleHttpErrors(statusResponse, endpoint);
|
|
944
|
+
const statusData = await statusResponse.json();
|
|
945
|
+
if (statusData.status === "FAILED") {
|
|
946
|
+
return {
|
|
947
|
+
id: taskId,
|
|
948
|
+
status: "failed",
|
|
949
|
+
error: statusData.error ?? "Unknown error"
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
if (statusData.status !== "COMPLETED") {
|
|
953
|
+
return {
|
|
954
|
+
id: taskId,
|
|
955
|
+
status: "processing"
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
const resultUrl = `${BASE_URL}/${endpoint}/requests/${taskId}`;
|
|
959
|
+
const resultResponse = await fetch(resultUrl, {
|
|
960
|
+
headers: { Authorization: `Key ${auth}` }
|
|
961
|
+
});
|
|
962
|
+
await handleHttpErrors(resultResponse, endpoint);
|
|
963
|
+
const output = await resultResponse.json();
|
|
964
|
+
return {
|
|
965
|
+
id: taskId,
|
|
966
|
+
status: "completed",
|
|
967
|
+
output
|
|
968
|
+
};
|
|
969
|
+
},
|
|
970
|
+
parseOutput(raw, outputMapping) {
|
|
971
|
+
const data = raw;
|
|
972
|
+
const path = outputMapping.extract_path;
|
|
973
|
+
if (path === "images[].url") {
|
|
974
|
+
const images = data.images;
|
|
975
|
+
if (!Array.isArray(images)) return [];
|
|
976
|
+
return images.map((img) => ({
|
|
977
|
+
type: outputMapping.type,
|
|
978
|
+
url: img.url,
|
|
979
|
+
content_type: img.content_type ?? outputMapping.content_type ?? "image/jpeg"
|
|
980
|
+
}));
|
|
981
|
+
}
|
|
982
|
+
if (path === "video.url") {
|
|
983
|
+
const video = data.video;
|
|
984
|
+
if (!video?.url) return [];
|
|
985
|
+
return [
|
|
986
|
+
{
|
|
987
|
+
type: outputMapping.type,
|
|
988
|
+
url: video.url,
|
|
989
|
+
content_type: video.content_type ?? outputMapping.content_type ?? "video/mp4"
|
|
990
|
+
}
|
|
991
|
+
];
|
|
992
|
+
}
|
|
993
|
+
if (path === "audio.url") {
|
|
994
|
+
const audio = data.audio;
|
|
995
|
+
if (!audio?.url) return [];
|
|
996
|
+
return [
|
|
997
|
+
{
|
|
998
|
+
type: outputMapping.type,
|
|
999
|
+
url: audio.url,
|
|
1000
|
+
content_type: audio.content_type ?? outputMapping.content_type ?? "audio/mpeg"
|
|
1001
|
+
}
|
|
1002
|
+
];
|
|
1003
|
+
}
|
|
1004
|
+
return [];
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
|
|
1008
|
+
// src/adapters/replicate.ts
|
|
1009
|
+
var BASE_URL2 = "https://api.replicate.com/v1";
|
|
1010
|
+
async function handleHttpErrors2(response, endpoint) {
|
|
1011
|
+
if (response.ok) return;
|
|
1012
|
+
const status = response.status;
|
|
1013
|
+
if (status === 401) {
|
|
1014
|
+
throw new AuthError("replicate", "REPLICATE_API_TOKEN");
|
|
1015
|
+
}
|
|
1016
|
+
if (status === 429) {
|
|
1017
|
+
const retryAfter = response.headers.get("retry-after");
|
|
1018
|
+
const retryMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : 6e4;
|
|
1019
|
+
throw new RateLimitError("replicate", retryMs);
|
|
1020
|
+
}
|
|
1021
|
+
let raw;
|
|
1022
|
+
try {
|
|
1023
|
+
raw = await response.json();
|
|
1024
|
+
} catch {
|
|
1025
|
+
raw = await response.text().catch(() => null);
|
|
1026
|
+
}
|
|
1027
|
+
throw new ProviderError("replicate", endpoint, status, raw);
|
|
1028
|
+
}
|
|
1029
|
+
function authHeaders2(auth) {
|
|
1030
|
+
return {
|
|
1031
|
+
Authorization: `Bearer ${auth}`,
|
|
1032
|
+
"Content-Type": "application/json"
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
function inferContentType(url) {
|
|
1036
|
+
const lower = url.toLowerCase();
|
|
1037
|
+
if (lower.includes(".png")) return "image/png";
|
|
1038
|
+
if (lower.includes(".jpg") || lower.includes(".jpeg")) return "image/jpeg";
|
|
1039
|
+
if (lower.includes(".webp")) return "image/webp";
|
|
1040
|
+
if (lower.includes(".mp4")) return "video/mp4";
|
|
1041
|
+
if (lower.includes(".mp3")) return "audio/mpeg";
|
|
1042
|
+
if (lower.includes(".wav")) return "audio/wav";
|
|
1043
|
+
return "image/jpeg";
|
|
1044
|
+
}
|
|
1045
|
+
var replicateAdapter = {
|
|
1046
|
+
name: "replicate",
|
|
1047
|
+
async submit(endpoint, params, auth) {
|
|
1048
|
+
const url = `${BASE_URL2}/models/${endpoint}/predictions`;
|
|
1049
|
+
const response = await fetch(url, {
|
|
1050
|
+
method: "POST",
|
|
1051
|
+
headers: authHeaders2(auth),
|
|
1052
|
+
body: JSON.stringify({ input: params })
|
|
1053
|
+
});
|
|
1054
|
+
await handleHttpErrors2(response, endpoint);
|
|
1055
|
+
const data = await response.json();
|
|
1056
|
+
return {
|
|
1057
|
+
id: data.id,
|
|
1058
|
+
status: "pending"
|
|
1059
|
+
};
|
|
1060
|
+
},
|
|
1061
|
+
async poll(taskId, auth) {
|
|
1062
|
+
const url = `${BASE_URL2}/predictions/${taskId}`;
|
|
1063
|
+
const response = await fetch(url, {
|
|
1064
|
+
headers: { Authorization: `Bearer ${auth}` }
|
|
1065
|
+
});
|
|
1066
|
+
await handleHttpErrors2(response, taskId);
|
|
1067
|
+
const data = await response.json();
|
|
1068
|
+
if (data.status === "succeeded") {
|
|
1069
|
+
return {
|
|
1070
|
+
id: data.id,
|
|
1071
|
+
status: "completed",
|
|
1072
|
+
output: data.output
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
if (data.status === "failed" || data.status === "canceled") {
|
|
1076
|
+
return {
|
|
1077
|
+
id: data.id,
|
|
1078
|
+
status: "failed",
|
|
1079
|
+
error: data.error ?? `Prediction ${data.status}`
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
return {
|
|
1083
|
+
id: data.id,
|
|
1084
|
+
status: "processing"
|
|
1085
|
+
};
|
|
1086
|
+
},
|
|
1087
|
+
parseOutput(raw, outputMapping) {
|
|
1088
|
+
if (!Array.isArray(raw)) return [];
|
|
1089
|
+
return raw.map((url) => ({
|
|
1090
|
+
type: outputMapping.type,
|
|
1091
|
+
url,
|
|
1092
|
+
content_type: outputMapping.content_type ?? inferContentType(url)
|
|
1093
|
+
}));
|
|
1094
|
+
}
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
// src/adapters/wavespeed.ts
|
|
1098
|
+
var BASE_URL3 = "https://api.wavespeed.ai/api/v3";
|
|
1099
|
+
async function handleHttpErrors3(response, endpoint) {
|
|
1100
|
+
if (response.ok) return;
|
|
1101
|
+
const status = response.status;
|
|
1102
|
+
if (status === 401) {
|
|
1103
|
+
throw new AuthError("wavespeed", "WAVESPEED_API_KEY");
|
|
1104
|
+
}
|
|
1105
|
+
if (status === 429) {
|
|
1106
|
+
const retryAfter = response.headers.get("retry-after");
|
|
1107
|
+
const retryMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : 6e4;
|
|
1108
|
+
throw new RateLimitError("wavespeed", retryMs);
|
|
1109
|
+
}
|
|
1110
|
+
let raw;
|
|
1111
|
+
try {
|
|
1112
|
+
raw = await response.json();
|
|
1113
|
+
} catch {
|
|
1114
|
+
raw = await response.text().catch(() => null);
|
|
1115
|
+
}
|
|
1116
|
+
throw new ProviderError("wavespeed", endpoint, status, raw);
|
|
1117
|
+
}
|
|
1118
|
+
function authHeaders3(auth) {
|
|
1119
|
+
return {
|
|
1120
|
+
Authorization: `Bearer ${auth}`,
|
|
1121
|
+
"Content-Type": "application/json"
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
function inferContentType2(url) {
|
|
1125
|
+
const ext = url.split(".").pop()?.toLowerCase()?.split("?")[0];
|
|
1126
|
+
switch (ext) {
|
|
1127
|
+
case "png":
|
|
1128
|
+
return "image/png";
|
|
1129
|
+
case "jpg":
|
|
1130
|
+
case "jpeg":
|
|
1131
|
+
return "image/jpeg";
|
|
1132
|
+
case "webp":
|
|
1133
|
+
return "image/webp";
|
|
1134
|
+
case "gif":
|
|
1135
|
+
return "image/gif";
|
|
1136
|
+
case "mp4":
|
|
1137
|
+
return "video/mp4";
|
|
1138
|
+
case "mp3":
|
|
1139
|
+
return "audio/mpeg";
|
|
1140
|
+
case "wav":
|
|
1141
|
+
return "audio/wav";
|
|
1142
|
+
default:
|
|
1143
|
+
return "application/octet-stream";
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
var wavespeedAdapter = {
|
|
1147
|
+
name: "wavespeed",
|
|
1148
|
+
async submit(endpoint, params, auth) {
|
|
1149
|
+
const url = `${BASE_URL3}/${endpoint}`;
|
|
1150
|
+
const response = await fetch(url, {
|
|
1151
|
+
method: "POST",
|
|
1152
|
+
headers: authHeaders3(auth),
|
|
1153
|
+
body: JSON.stringify(params)
|
|
1154
|
+
});
|
|
1155
|
+
await handleHttpErrors3(response, endpoint);
|
|
1156
|
+
const json = await response.json();
|
|
1157
|
+
return {
|
|
1158
|
+
id: json.data.id,
|
|
1159
|
+
status: "pending"
|
|
1160
|
+
};
|
|
1161
|
+
},
|
|
1162
|
+
async poll(taskId, auth) {
|
|
1163
|
+
const url = `${BASE_URL3}/predictions/${taskId}/result`;
|
|
1164
|
+
const response = await fetch(url, {
|
|
1165
|
+
headers: { Authorization: `Bearer ${auth}` }
|
|
1166
|
+
});
|
|
1167
|
+
await handleHttpErrors3(response, `predictions/${taskId}/result`);
|
|
1168
|
+
const json = await response.json();
|
|
1169
|
+
const { data } = json;
|
|
1170
|
+
if (data.status === "failed") {
|
|
1171
|
+
return {
|
|
1172
|
+
id: taskId,
|
|
1173
|
+
status: "failed",
|
|
1174
|
+
error: data.error ?? "Unknown error"
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
if (data.status === "completed") {
|
|
1178
|
+
return {
|
|
1179
|
+
id: taskId,
|
|
1180
|
+
status: "completed",
|
|
1181
|
+
output: data
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
return {
|
|
1185
|
+
id: taskId,
|
|
1186
|
+
status: "processing"
|
|
1187
|
+
};
|
|
1188
|
+
},
|
|
1189
|
+
parseOutput(raw, outputMapping) {
|
|
1190
|
+
const data = raw;
|
|
1191
|
+
const outputs = data.outputs;
|
|
1192
|
+
if (!Array.isArray(outputs)) return [];
|
|
1193
|
+
return outputs.map((url) => ({
|
|
1194
|
+
type: outputMapping.type,
|
|
1195
|
+
url,
|
|
1196
|
+
content_type: outputMapping.content_type ?? inferContentType2(url)
|
|
1197
|
+
}));
|
|
1198
|
+
}
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
// src/retry.ts
|
|
1202
|
+
var DEFAULT_OPTIONS = {
|
|
1203
|
+
maxRetries: 3,
|
|
1204
|
+
initialDelayMs: 1e3,
|
|
1205
|
+
maxDelayMs: 1e4,
|
|
1206
|
+
timeoutMs: 3e5
|
|
1207
|
+
};
|
|
1208
|
+
function sleep(ms) {
|
|
1209
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1210
|
+
}
|
|
1211
|
+
function isRetryable(error) {
|
|
1212
|
+
if (error instanceof AuthError || error instanceof ValidationError || error instanceof ModelNotFoundError) {
|
|
1213
|
+
return false;
|
|
1214
|
+
}
|
|
1215
|
+
if (error instanceof RateLimitError) {
|
|
1216
|
+
return true;
|
|
1217
|
+
}
|
|
1218
|
+
if (error instanceof ProviderError) {
|
|
1219
|
+
return error.statusCode >= 500;
|
|
1220
|
+
}
|
|
1221
|
+
if (error instanceof TypeError) {
|
|
1222
|
+
return true;
|
|
1223
|
+
}
|
|
1224
|
+
return false;
|
|
1225
|
+
}
|
|
1226
|
+
function getDelayMs(error, attempt, options) {
|
|
1227
|
+
if (error instanceof RateLimitError) {
|
|
1228
|
+
return error.retryAfterMs;
|
|
1229
|
+
}
|
|
1230
|
+
const jitter = Math.random() * options.initialDelayMs * 0.5;
|
|
1231
|
+
const delay = options.initialDelayMs * Math.pow(2, attempt) + jitter;
|
|
1232
|
+
return Math.min(delay, options.maxDelayMs);
|
|
1233
|
+
}
|
|
1234
|
+
async function withRetry(fn, options) {
|
|
1235
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
1236
|
+
const startTime = Date.now();
|
|
1237
|
+
let lastError;
|
|
1238
|
+
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
|
|
1239
|
+
if (attempt > 0) {
|
|
1240
|
+
const elapsed = Date.now() - startTime;
|
|
1241
|
+
if (elapsed >= opts.timeoutMs) {
|
|
1242
|
+
throw new TimeoutError("unknown", "unknown", opts.timeoutMs);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
try {
|
|
1246
|
+
return await fn();
|
|
1247
|
+
} catch (error) {
|
|
1248
|
+
lastError = error;
|
|
1249
|
+
if (!isRetryable(error)) {
|
|
1250
|
+
throw error;
|
|
1251
|
+
}
|
|
1252
|
+
if (attempt >= opts.maxRetries) {
|
|
1253
|
+
throw error;
|
|
1254
|
+
}
|
|
1255
|
+
const delay = getDelayMs(error, attempt, opts);
|
|
1256
|
+
const elapsed = Date.now() - startTime;
|
|
1257
|
+
if (elapsed + delay >= opts.timeoutMs) {
|
|
1258
|
+
throw new TimeoutError("unknown", "unknown", opts.timeoutMs);
|
|
1259
|
+
}
|
|
1260
|
+
await sleep(delay);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
throw lastError;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
// src/gateway.ts
|
|
1267
|
+
var adapters = {
|
|
1268
|
+
"fal-ai": falAiAdapter,
|
|
1269
|
+
"replicate": replicateAdapter,
|
|
1270
|
+
"wavespeed": wavespeedAdapter
|
|
1271
|
+
};
|
|
1272
|
+
async function generate(request) {
|
|
1273
|
+
const startTime = Date.now();
|
|
1274
|
+
if (!request.model) throw new ValidationError("model", "model is required");
|
|
1275
|
+
const auth = new AuthManager();
|
|
1276
|
+
const model = resolveModel(request.model, auth.availableProviders());
|
|
1277
|
+
const availableBindings = model.providers.filter(
|
|
1278
|
+
(p) => auth.availableProviders().includes(p.provider) && adapters[p.provider]
|
|
1279
|
+
);
|
|
1280
|
+
if (availableBindings.length === 0) {
|
|
1281
|
+
throw new ValidationError(
|
|
1282
|
+
"model",
|
|
1283
|
+
`No adapter available for model "${model.canonical_name}". Available providers: ${model.providers.map((p) => p.provider).join(", ")}`
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1286
|
+
const binding = availableBindings[0];
|
|
1287
|
+
const template = getCategoryTemplate(model.category);
|
|
1288
|
+
if (!template) {
|
|
1289
|
+
throw new ValidationError("model", `No category template for "${model.category}" yet`);
|
|
1290
|
+
}
|
|
1291
|
+
const providerParams = mapInput(request, binding, template);
|
|
1292
|
+
const adapter = adapters[binding.provider];
|
|
1293
|
+
const apiKey = auth.getKey(binding.provider);
|
|
1294
|
+
const timeoutMs = request.options?.timeout ?? template.default_timeout_ms;
|
|
1295
|
+
const submitted = await withRetry(
|
|
1296
|
+
() => adapter.submit(binding.endpoint, providerParams, apiKey),
|
|
1297
|
+
{ timeoutMs }
|
|
1298
|
+
);
|
|
1299
|
+
let result = submitted;
|
|
1300
|
+
while (result.status === "processing" || result.status === "pending") {
|
|
1301
|
+
await new Promise((resolve2) => setTimeout(resolve2, 1e3));
|
|
1302
|
+
result = await adapter.poll(submitted.id, apiKey, binding.endpoint);
|
|
1303
|
+
}
|
|
1304
|
+
if (result.status === "failed") {
|
|
1305
|
+
throw new ProviderError(
|
|
1306
|
+
binding.provider,
|
|
1307
|
+
model.canonical_name,
|
|
1308
|
+
0,
|
|
1309
|
+
result.error || "Generation failed"
|
|
1310
|
+
);
|
|
1311
|
+
}
|
|
1312
|
+
const outputs = mapOutput(result.output, binding.output_map);
|
|
1313
|
+
return {
|
|
1314
|
+
id: randomUUID(),
|
|
1315
|
+
model: model.canonical_name,
|
|
1316
|
+
provider: binding.provider,
|
|
1317
|
+
status: "completed",
|
|
1318
|
+
outputs,
|
|
1319
|
+
metadata: {
|
|
1320
|
+
inference_time_ms: Date.now() - startTime,
|
|
1321
|
+
seed: typeof result.output === "object" && result.output !== null ? result.output.seed : void 0,
|
|
1322
|
+
safety_flagged: typeof result.output === "object" && result.output !== null ? Array.isArray(result.output.has_nsfw_concepts) ? result.output.has_nsfw_concepts.some((v) => v) : void 0 : void 0
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
// src/discovery.ts
|
|
1328
|
+
function listModels(filters) {
|
|
1329
|
+
const auth = new AuthManager();
|
|
1330
|
+
let models = auth.listAvailableModels(loadRegistry());
|
|
1331
|
+
if (filters?.category) {
|
|
1332
|
+
models = models.filter((m) => m.category === filters.category);
|
|
1333
|
+
}
|
|
1334
|
+
if (filters?.provider) {
|
|
1335
|
+
models = models.filter(
|
|
1336
|
+
(m) => m.providers.some((p) => p.provider === filters.provider)
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
if (filters?.query) {
|
|
1340
|
+
const q = filters.query.toLowerCase();
|
|
1341
|
+
models = models.filter(
|
|
1342
|
+
(m) => m.canonical_name.includes(q) || m.aliases.some((a) => a.includes(q))
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
return models;
|
|
1346
|
+
}
|
|
1347
|
+
function getModel(name) {
|
|
1348
|
+
return resolveModel(name);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
export {
|
|
1352
|
+
GetAIApiError,
|
|
1353
|
+
AuthError,
|
|
1354
|
+
ModelNotFoundError,
|
|
1355
|
+
ValidationError,
|
|
1356
|
+
ProviderError,
|
|
1357
|
+
TimeoutError,
|
|
1358
|
+
RateLimitError,
|
|
1359
|
+
generate,
|
|
1360
|
+
listModels,
|
|
1361
|
+
getModel
|
|
1362
|
+
};
|
|
1363
|
+
//# sourceMappingURL=chunk-O4TLMX4T.js.map
|