@broberg/ai-sdk 0.10.1 → 0.10.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.d.ts CHANGED
@@ -164,6 +164,9 @@ interface ImageRequest {
164
164
  height?: number;
165
165
  /** LoRAs to merge at inference (F021) — e.g. a trained brand/style LoRA. */
166
166
  loras?: LoraWeight[];
167
+ /** F021.4 — re-roll once with a fresh seed if fal's safety-checker false-positives
168
+ * and returns a black image (has_nsfw_concepts). fal only. */
169
+ retryOnBlack?: boolean;
167
170
  }
168
171
  interface ImageResult {
169
172
  url: string;
@@ -929,6 +932,8 @@ declare const imageInputSchema: z.ZodObject<{
929
932
  }>, "many">>;
930
933
  /** Shorthand for a single LoRA — normalized to loras:[{path, scale:1}]. */
931
934
  lora: z.ZodOptional<z.ZodString>;
935
+ /** F021.4 — re-roll once if fal returns a black image (NSFW false-positive). */
936
+ retryOnBlack: z.ZodOptional<z.ZodBoolean>;
932
937
  }, "strip", z.ZodTypeAny, {
933
938
  prompt: string;
934
939
  purpose?: string | undefined;
@@ -951,6 +956,7 @@ declare const imageInputSchema: z.ZodObject<{
951
956
  labels?: Record<string, string> | undefined;
952
957
  width?: number | undefined;
953
958
  height?: number | undefined;
959
+ retryOnBlack?: boolean | undefined;
954
960
  }, {
955
961
  prompt: string;
956
962
  purpose?: string | undefined;
@@ -973,6 +979,7 @@ declare const imageInputSchema: z.ZodObject<{
973
979
  labels?: Record<string, string> | undefined;
974
980
  width?: number | undefined;
975
981
  height?: number | undefined;
982
+ retryOnBlack?: boolean | undefined;
976
983
  }>;
977
984
  declare const trainStyleInputSchema: z.ZodObject<{
978
985
  tier: z.ZodOptional<z.ZodEnum<["fast", "smart", "powerful", "cheap", "vision", "video", "embedding"]>>;
@@ -1680,8 +1687,8 @@ declare const falStubAdapter: ProviderAdapter;
1680
1687
  * wires the live adapters. */
1681
1688
  declare const stubProviders: Record<string, ProviderAdapter>;
1682
1689
 
1683
- declare const VERSION: "0.10.1";
1684
- declare const SDK_TAG: "@broberg/ai-sdk@0.10.1";
1690
+ declare const VERSION: "0.10.3";
1691
+ declare const SDK_TAG: "@broberg/ai-sdk@0.10.3";
1685
1692
 
1686
1693
  /** Built-in defaults. Every entry is overridable via AiConfig.defaults or a
1687
1694
  * per-call override. Model IDs are current at scaffold time; callers pin their
package/dist/index.js CHANGED
@@ -1317,7 +1317,13 @@ function falAdapter(config = {}) {
1317
1317
  body.loras = req.loras.map((l) => ({ path: l.path, scale: l.scale ?? 1 }));
1318
1318
  }
1319
1319
  const mode = config.mode ?? "sync";
1320
- const url = await (mode === "sync" ? runSync(req.spec.model, headers, body) : runQueue(req.spec.model, headers, body));
1320
+ const run = (b) => mode === "sync" ? runSync(req.spec.model, headers, b) : runQueue(req.spec.model, headers, b);
1321
+ let calls = 1;
1322
+ let { url, flagged } = await run(body);
1323
+ if (flagged && req.retryOnBlack) {
1324
+ calls++;
1325
+ ({ url, flagged } = await run({ ...body, seed: Math.floor(Math.random() * 1e9) }));
1326
+ }
1321
1327
  const usage = freshUsage({
1322
1328
  provider: "fal",
1323
1329
  model: req.spec.model,
@@ -1326,7 +1332,7 @@ function falAdapter(config = {}) {
1326
1332
  inputTokens: 0,
1327
1333
  outputTokens: 0
1328
1334
  });
1329
- usage.costUsd = config.pricePerImage ?? FAL_IMAGE_PRICE_ESTIMATE[req.spec.model] ?? 0;
1335
+ usage.costUsd = (config.pricePerImage ?? FAL_IMAGE_PRICE_ESTIMATE[req.spec.model] ?? 0) * calls;
1330
1336
  return { url, usage };
1331
1337
  }
1332
1338
  async function trainStyle(req) {
@@ -1334,7 +1340,7 @@ function falAdapter(config = {}) {
1334
1340
  if (!apiKey) throw new Error("fal adapter: FAL_KEY not set");
1335
1341
  const headers = authHeaders(apiKey);
1336
1342
  const body = {
1337
- images_data_url: await resolveImagesDataUrl(req.images),
1343
+ images_data_url: await resolveImagesUrl(req.images, apiKey),
1338
1344
  is_style: req.isStyle ?? true
1339
1345
  };
1340
1346
  if (req.triggerWord !== void 0) body.trigger_word = req.triggerWord;
@@ -1365,7 +1371,7 @@ function falAdapter(config = {}) {
1365
1371
  usage.costUsd = config.pricePerTraining ?? FAL_TRAIN_PRICE_ESTIMATE;
1366
1372
  return { loraUrl, configUrl: configUrl ?? "", usage };
1367
1373
  }
1368
- async function resolveImagesDataUrl(images) {
1374
+ async function resolveImagesUrl(images, apiKey) {
1369
1375
  if (typeof images === "string") return images;
1370
1376
  const files = await Promise.all(
1371
1377
  images.map(async (url, i) => {
@@ -1374,8 +1380,27 @@ function falAdapter(config = {}) {
1374
1380
  return { name: fileNameFromUrl(url, i), data: new Uint8Array(await res.arrayBuffer()) };
1375
1381
  })
1376
1382
  );
1377
- const zip = buildZip(files);
1378
- return `data:application/zip;base64,${Buffer.from(zip).toString("base64")}`;
1383
+ return uploadToFalStorage(buildZip(files), "application/zip", "styleset.zip", apiKey);
1384
+ }
1385
+ async function uploadToFalStorage(bytes, contentType, fileName, apiKey) {
1386
+ const initiate = await doFetch("https://rest.alpha.fal.ai/storage/upload/initiate", {
1387
+ method: "POST",
1388
+ headers: { Authorization: `Key ${apiKey}`, "content-type": "application/json" },
1389
+ body: JSON.stringify({ content_type: contentType, file_name: fileName })
1390
+ });
1391
+ if (!initiate.ok) {
1392
+ throw new Error(
1393
+ `fal storage initiate ${initiate.status}: ${(await initiate.text().catch(() => "")).slice(0, 200)}`
1394
+ );
1395
+ }
1396
+ const { upload_url, file_url } = await initiate.json();
1397
+ const put = await doFetch(upload_url, {
1398
+ method: "PUT",
1399
+ headers: { "content-type": contentType },
1400
+ body: bytes
1401
+ });
1402
+ if (!put.ok) throw new Error(`fal storage upload PUT ${put.status}`);
1403
+ return file_url;
1379
1404
  }
1380
1405
  async function runSync(model, headers, body) {
1381
1406
  const res = await doFetch(`${syncBase}/${model}`, {
@@ -1389,13 +1414,13 @@ function falAdapter(config = {}) {
1389
1414
  const data = await res.json();
1390
1415
  const out = data.images?.[0]?.url;
1391
1416
  if (!out) throw new Error(`fal: no image url in response`);
1392
- return out;
1417
+ return { url: out, flagged: data.has_nsfw_concepts?.[0] === true };
1393
1418
  }
1394
1419
  async function runQueue(model, headers, body) {
1395
1420
  const result = await queueResult(model, headers, body, timeoutMs);
1396
1421
  const out = result.images?.[0]?.url;
1397
1422
  if (!out) throw new Error("fal queue: no image url in result");
1398
- return out;
1423
+ return { url: out, flagged: result.has_nsfw_concepts?.[0] === true };
1399
1424
  }
1400
1425
  async function queueResult(model, headers, body, deadlineMs) {
1401
1426
  const submitRes = await doFetch(`${queueBase}/${model}`, {
@@ -1835,6 +1860,8 @@ var imageInputSchema = z.object({
1835
1860
  loras: z.array(loraWeightSchema).optional(),
1836
1861
  /** Shorthand for a single LoRA — normalized to loras:[{path, scale:1}]. */
1837
1862
  lora: z.string().optional(),
1863
+ /** F021.4 — re-roll once if fal returns a black image (NSFW false-positive). */
1864
+ retryOnBlack: z.boolean().optional(),
1838
1865
  ...callOptions
1839
1866
  });
1840
1867
  var trainStyleInputSchema = z.object({
@@ -2164,7 +2191,8 @@ function createAI(config = {}) {
2164
2191
  spec,
2165
2192
  width: input.width,
2166
2193
  height: input.height,
2167
- loras: loras.length ? loras : void 0
2194
+ loras: loras.length ? loras : void 0,
2195
+ retryOnBlack: input.retryOnBlack
2168
2196
  });
2169
2197
  }
2170
2198
  });
@@ -2419,8 +2447,8 @@ var stubProviders = {
2419
2447
  };
2420
2448
 
2421
2449
  // src/version.ts
2422
- var VERSION = "0.10.1";
2423
- var SDK_TAG = "@broberg/ai-sdk@0.10.1";
2450
+ var VERSION = "0.10.3";
2451
+ var SDK_TAG = "@broberg/ai-sdk@0.10.3";
2424
2452
 
2425
2453
  // src/cost/budget-store.ts
2426
2454
  function sqliteBudgetStore(config) {