@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 +51 -0
- package/dist/index.cjs +34 -6
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +34 -6
- package/package.json +1 -1
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)(
|
|
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(
|
|
2674
|
+
return ok({
|
|
2675
|
+
...obj,
|
|
2676
|
+
requestId
|
|
2677
|
+
});
|
|
2650
2678
|
}
|
|
2651
2679
|
};
|
|
2652
2680
|
var MotifError = class extends Error {
|