@howells/motif-sdk 0.1.0 → 0.2.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/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # @howells/motif-sdk
2
+
3
+ Public Node SDK for Motif fal.ai generation, editing, utility tools, and model metadata.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @howells/motif-sdk
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { MotifServer, FAL_TOOLS, MODELS } from "@howells/motif-sdk";
15
+
16
+ const motif = new MotifServer(process.env.FAL_KEY!);
17
+
18
+ const result = await motif.generate({
19
+ model: "banana2",
20
+ prompt: "editorial product photo",
21
+ ephemeral: true,
22
+ });
23
+
24
+ if (result.isErr()) {
25
+ throw result.error;
26
+ }
27
+
28
+ console.log(result.value.images[0]?.url);
29
+ ```
30
+
31
+ ## Main Exports
32
+
33
+ - `MotifServer` - Result-returning fal client for generation, queue jobs, upload, utility tools, and payload deletion.
34
+ - `buildGenerateBody` - Pure fal request normalization for dry runs and tests.
35
+ - `MODELS` - Motif model aliases, fal endpoints, capabilities, pricing, and benchmarks.
36
+ - `FAL_TOOLS` - Normalized fal utility endpoints such as SAM, depth, upscaling, moderation, and background removal.
37
+ - `getFalKeyFromEnv` - `@howells/envy` backed `FAL_KEY` parsing.
38
+
39
+ ## Testing
40
+
41
+ ```bash
42
+ pnpm --filter @howells/motif-sdk test
43
+ pnpm --filter @howells/motif-sdk typecheck
44
+ ```
45
+
46
+ Live fal canaries are opt-in:
47
+
48
+ ```bash
49
+ RUN_FAL_CANARY=1 pnpm --filter @howells/motif-sdk test -- tests/fal-canary.test.ts
50
+ ```
51
+
package/dist/index.cjs CHANGED
@@ -2261,6 +2261,7 @@ function buildFalToolRequest(options) {
2261
2261
  // src/server.ts
2262
2262
  var FAL_BASE_URL = "https://fal.run";
2263
2263
  var FAL_QUEUE_URL = "https://queue.fal.run";
2264
+ var FAL_API_URL = "https://api.fal.ai";
2264
2265
  var FAL_REST_URL = "https://rest.alpha.fal.ai";
2265
2266
  function endpointFromQueueUrl(url, fallback) {
2266
2267
  if (!url) return fallback;
@@ -2300,7 +2301,8 @@ var MotifServer = class {
2300
2301
  const { endpoint, body } = buildGenerateBody(options);
2301
2302
  const response = await this.request(`${FAL_BASE_URL}/${endpoint}`, {
2302
2303
  method: "POST",
2303
- body: JSON.stringify(body)
2304
+ body: JSON.stringify(body),
2305
+ headers: this.ephemeralHeaders(options)
2304
2306
  });
2305
2307
  if (response.isErr()) {
2306
2308
  return (0, import_neverthrow.err)(response.error);
@@ -2341,7 +2343,8 @@ var MotifServer = class {
2341
2343
  const { endpoint, body } = buildGenerateBody(options);
2342
2344
  const response = await this.request(`${FAL_QUEUE_URL}/${endpoint}`, {
2343
2345
  method: "POST",
2344
- body: JSON.stringify(body)
2346
+ body: JSON.stringify(body),
2347
+ headers: this.ephemeralHeaders(options)
2345
2348
  });
2346
2349
  if (response.isErr()) {
2347
2350
  return (0, import_neverthrow.err)(response.error);
@@ -2392,7 +2395,7 @@ var MotifServer = class {
2392
2395
  return (0, import_neverthrow.err)(response.error);
2393
2396
  }
2394
2397
  const data = await response.value.json();
2395
- return this.normalizeResponse(data);
2398
+ return this.normalizeResponse(data, requestId);
2396
2399
  }
2397
2400
  /** ─── Processing ──────────────────────────────────────────── */
2398
2401
  /** Upscale an image using clarity or crystal upscaler. */
@@ -2607,6 +2610,23 @@ var MotifServer = class {
2607
2610
  }
2608
2611
  return (0, import_neverthrow.ok)(await response.value.json());
2609
2612
  }
2613
+ /**
2614
+ * Delete fal's stored IO payloads for a completed request.
2615
+ *
2616
+ * This removes request input/output payload files exposed by fal's payloads
2617
+ * API. It does not remove billing/account metadata or input files separately
2618
+ * uploaded to fal storage before a request.
2619
+ */
2620
+ async deletePayloads(requestId) {
2621
+ const response = await this.request(
2622
+ `${FAL_API_URL}/v1/models/requests/${encodeURIComponent(requestId)}/payloads`,
2623
+ { method: "DELETE" }
2624
+ );
2625
+ if (response.isErr()) {
2626
+ return (0, import_neverthrow.err)(response.error);
2627
+ }
2628
+ return (0, import_neverthrow.ok)(void 0);
2629
+ }
2610
2630
  /** Estimate cost for a generation (no API call). */
2611
2631
  estimateCost(model, resolution, numImages) {
2612
2632
  return estimateCost(model, resolution, numImages);
@@ -2681,12 +2701,16 @@ var MotifServer = class {
2681
2701
  }
2682
2702
  return (0, import_neverthrow.err)(lastError ?? new MotifError("Request failed after retries", 0));
2683
2703
  }
2704
+ ephemeralHeaders(options) {
2705
+ return options.ephemeral ? { "X-Fal-Store-IO": "0" } : {};
2706
+ }
2684
2707
  /**
2685
2708
  * Normalize fal.ai responses.
2686
2709
  * Some APIs return `{ image: {...} }` instead of `{ images: [...] }`.
2687
2710
  */
2688
- normalizeResponse(data) {
2711
+ normalizeResponse(data, fallbackRequestId) {
2689
2712
  const obj = data;
2713
+ const requestId = obj.request_id ?? obj.requestId ?? fallbackRequestId;
2690
2714
  if ("detail" in obj) {
2691
2715
  return (0, import_neverthrow.err)(
2692
2716
  new MotifError(obj.detail, 0, "FAL_ERROR")
@@ -2696,10 +2720,14 @@ var MotifServer = class {
2696
2720
  return (0, import_neverthrow.ok)({
2697
2721
  images: [obj.image],
2698
2722
  seed: obj.seed,
2699
- prompt: obj.prompt
2723
+ prompt: obj.prompt,
2724
+ requestId
2700
2725
  });
2701
2726
  }
2702
- return (0, import_neverthrow.ok)(obj);
2727
+ return (0, import_neverthrow.ok)({
2728
+ ...obj,
2729
+ requestId
2730
+ });
2703
2731
  }
2704
2732
  };
2705
2733
  var MotifError = class extends Error {
package/dist/index.d.cts CHANGED
@@ -105,6 +105,8 @@ interface GenerateOptions {
105
105
  /** GPT background mode where supported */
106
106
  background?: BackgroundMode;
107
107
  editImageUrls?: string[];
108
+ /** Ask fal not to store IO payloads, and expose request ids for deletion. */
109
+ ephemeral?: boolean;
108
110
  /** Google-search alias for fal models that expose enable_google_search */
109
111
  enableGoogleSearch?: boolean;
110
112
  /** fal safety checker toggle where supported */
@@ -223,6 +225,7 @@ interface MotifImage {
223
225
  interface MotifResponse {
224
226
  images: MotifImage[];
225
227
  prompt?: string;
228
+ requestId?: string;
226
229
  seed?: number;
227
230
  }
228
231
  /** ─── Queue Types ────────────────────────────────────────────── */
@@ -387,6 +390,14 @@ declare class MotifServer {
387
390
  /** ─── Utilities ───────────────────────────────────────────── */
388
391
  /** Run a registered fal utility/tool endpoint. */
389
392
  runTool(options: ToolRunOptions): Promise<Result<ToolResponse, MotifError>>;
393
+ /**
394
+ * Delete fal's stored IO payloads for a completed request.
395
+ *
396
+ * This removes request input/output payload files exposed by fal's payloads
397
+ * API. It does not remove billing/account metadata or input files separately
398
+ * uploaded to fal storage before a request.
399
+ */
400
+ deletePayloads(requestId: string): Promise<Result<void, MotifError>>;
390
401
  /** Estimate cost for a generation (no API call). */
391
402
  estimateCost(model: string, resolution?: Resolution, numImages?: number): number;
392
403
  /** Build the fal.ai request body without sending it. */
@@ -743,6 +754,7 @@ declare class MotifServer {
743
754
  /** ─── Private ─────────────────────────────────────────────── */
744
755
  /** Authenticated fetch to fal.ai APIs with retry logic. */
745
756
  private request;
757
+ private ephemeralHeaders;
746
758
  /**
747
759
  * Normalize fal.ai responses.
748
760
  * Some APIs return `{ image: {...} }` instead of `{ images: [...] }`.
package/dist/index.d.ts CHANGED
@@ -105,6 +105,8 @@ interface GenerateOptions {
105
105
  /** GPT background mode where supported */
106
106
  background?: BackgroundMode;
107
107
  editImageUrls?: string[];
108
+ /** Ask fal not to store IO payloads, and expose request ids for deletion. */
109
+ ephemeral?: boolean;
108
110
  /** Google-search alias for fal models that expose enable_google_search */
109
111
  enableGoogleSearch?: boolean;
110
112
  /** fal safety checker toggle where supported */
@@ -223,6 +225,7 @@ interface MotifImage {
223
225
  interface MotifResponse {
224
226
  images: MotifImage[];
225
227
  prompt?: string;
228
+ requestId?: string;
226
229
  seed?: number;
227
230
  }
228
231
  /** ─── Queue Types ────────────────────────────────────────────── */
@@ -387,6 +390,14 @@ declare class MotifServer {
387
390
  /** ─── Utilities ───────────────────────────────────────────── */
388
391
  /** Run a registered fal utility/tool endpoint. */
389
392
  runTool(options: ToolRunOptions): Promise<Result<ToolResponse, MotifError>>;
393
+ /**
394
+ * Delete fal's stored IO payloads for a completed request.
395
+ *
396
+ * This removes request input/output payload files exposed by fal's payloads
397
+ * API. It does not remove billing/account metadata or input files separately
398
+ * uploaded to fal storage before a request.
399
+ */
400
+ deletePayloads(requestId: string): Promise<Result<void, MotifError>>;
390
401
  /** Estimate cost for a generation (no API call). */
391
402
  estimateCost(model: string, resolution?: Resolution, numImages?: number): number;
392
403
  /** Build the fal.ai request body without sending it. */
@@ -743,6 +754,7 @@ declare class MotifServer {
743
754
  /** ─── Private ─────────────────────────────────────────────── */
744
755
  /** Authenticated fetch to fal.ai APIs with retry logic. */
745
756
  private request;
757
+ private ephemeralHeaders;
746
758
  /**
747
759
  * Normalize fal.ai responses.
748
760
  * Some APIs return `{ image: {...} }` instead of `{ images: [...] }`.
package/dist/index.js CHANGED
@@ -2208,6 +2208,7 @@ function buildFalToolRequest(options) {
2208
2208
  // src/server.ts
2209
2209
  var FAL_BASE_URL = "https://fal.run";
2210
2210
  var FAL_QUEUE_URL = "https://queue.fal.run";
2211
+ var FAL_API_URL = "https://api.fal.ai";
2211
2212
  var FAL_REST_URL = "https://rest.alpha.fal.ai";
2212
2213
  function endpointFromQueueUrl(url, fallback) {
2213
2214
  if (!url) return fallback;
@@ -2247,7 +2248,8 @@ var MotifServer = class {
2247
2248
  const { endpoint, body } = buildGenerateBody(options);
2248
2249
  const response = await this.request(`${FAL_BASE_URL}/${endpoint}`, {
2249
2250
  method: "POST",
2250
- body: JSON.stringify(body)
2251
+ body: JSON.stringify(body),
2252
+ headers: this.ephemeralHeaders(options)
2251
2253
  });
2252
2254
  if (response.isErr()) {
2253
2255
  return err(response.error);
@@ -2288,7 +2290,8 @@ var MotifServer = class {
2288
2290
  const { endpoint, body } = buildGenerateBody(options);
2289
2291
  const response = await this.request(`${FAL_QUEUE_URL}/${endpoint}`, {
2290
2292
  method: "POST",
2291
- body: JSON.stringify(body)
2293
+ body: JSON.stringify(body),
2294
+ headers: this.ephemeralHeaders(options)
2292
2295
  });
2293
2296
  if (response.isErr()) {
2294
2297
  return err(response.error);
@@ -2339,7 +2342,7 @@ var MotifServer = class {
2339
2342
  return err(response.error);
2340
2343
  }
2341
2344
  const data = await response.value.json();
2342
- return this.normalizeResponse(data);
2345
+ return this.normalizeResponse(data, requestId);
2343
2346
  }
2344
2347
  /** ─── Processing ──────────────────────────────────────────── */
2345
2348
  /** Upscale an image using clarity or crystal upscaler. */
@@ -2554,6 +2557,23 @@ var MotifServer = class {
2554
2557
  }
2555
2558
  return ok(await response.value.json());
2556
2559
  }
2560
+ /**
2561
+ * Delete fal's stored IO payloads for a completed request.
2562
+ *
2563
+ * This removes request input/output payload files exposed by fal's payloads
2564
+ * API. It does not remove billing/account metadata or input files separately
2565
+ * uploaded to fal storage before a request.
2566
+ */
2567
+ async deletePayloads(requestId) {
2568
+ const response = await this.request(
2569
+ `${FAL_API_URL}/v1/models/requests/${encodeURIComponent(requestId)}/payloads`,
2570
+ { method: "DELETE" }
2571
+ );
2572
+ if (response.isErr()) {
2573
+ return err(response.error);
2574
+ }
2575
+ return ok(void 0);
2576
+ }
2557
2577
  /** Estimate cost for a generation (no API call). */
2558
2578
  estimateCost(model, resolution, numImages) {
2559
2579
  return estimateCost(model, resolution, numImages);
@@ -2628,12 +2648,16 @@ var MotifServer = class {
2628
2648
  }
2629
2649
  return err(lastError ?? new MotifError("Request failed after retries", 0));
2630
2650
  }
2651
+ ephemeralHeaders(options) {
2652
+ return options.ephemeral ? { "X-Fal-Store-IO": "0" } : {};
2653
+ }
2631
2654
  /**
2632
2655
  * Normalize fal.ai responses.
2633
2656
  * Some APIs return `{ image: {...} }` instead of `{ images: [...] }`.
2634
2657
  */
2635
- normalizeResponse(data) {
2658
+ normalizeResponse(data, fallbackRequestId) {
2636
2659
  const obj = data;
2660
+ const requestId = obj.request_id ?? obj.requestId ?? fallbackRequestId;
2637
2661
  if ("detail" in obj) {
2638
2662
  return err(
2639
2663
  new MotifError(obj.detail, 0, "FAL_ERROR")
@@ -2643,10 +2667,14 @@ var MotifServer = class {
2643
2667
  return ok({
2644
2668
  images: [obj.image],
2645
2669
  seed: obj.seed,
2646
- prompt: obj.prompt
2670
+ prompt: obj.prompt,
2671
+ requestId
2647
2672
  });
2648
2673
  }
2649
- return ok(obj);
2674
+ return ok({
2675
+ ...obj,
2676
+ requestId
2677
+ });
2650
2678
  }
2651
2679
  };
2652
2680
  var MotifError = class extends Error {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@howells/motif-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Public Node SDK for Motif fal.ai generation, editing, utility tools, and model metadata.",
5
5
  "license": "MIT",
6
6
  "author": "Daniel Howells",