@howells/motif-sdk 0.1.0 → 0.3.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 +131 -0
- package/dist/index.cjs +196 -9
- package/dist/index.d.cts +191 -1
- package/dist/index.d.ts +191 -1
- package/dist/index.js +190 -8
- package/package.json +13 -12
- package/LICENSE +0 -21
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
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
|
+
## Generate Images
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { MotifServer } from "@howells/motif-sdk";
|
|
15
|
+
|
|
16
|
+
const motif = new MotifServer({
|
|
17
|
+
apiKey: process.env.FAL_KEY!,
|
|
18
|
+
retries: 3,
|
|
19
|
+
timeout: 120_000,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const result = await motif.generate({
|
|
23
|
+
model: "banana2",
|
|
24
|
+
prompt: "editorial product photo",
|
|
25
|
+
resolution: "2K",
|
|
26
|
+
enableGoogleSearch: true,
|
|
27
|
+
ephemeral: true,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (result.isErr()) {
|
|
31
|
+
throw result.error;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(result.value.images[0]?.url);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Every async SDK method returns `Result<T, MotifError>` from `neverthrow`. Methods do not throw for fal request failures; check `isErr()` / `isOk()`.
|
|
38
|
+
|
|
39
|
+
## Dry-Run Request Bodies
|
|
40
|
+
|
|
41
|
+
Use `buildGenerateBody` or `motif.buildRequestBody()` when you need the exact fal endpoint and request body without making an API call.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { buildGenerateBody } from "@howells/motif-sdk";
|
|
45
|
+
|
|
46
|
+
const preview = buildGenerateBody({
|
|
47
|
+
model: "gpt2",
|
|
48
|
+
prompt: "change the wall color",
|
|
49
|
+
editImageUrls: ["https://example.com/interior.png"],
|
|
50
|
+
imageSize: "1536x1024",
|
|
51
|
+
maskImageUrl: "https://example.com/wall-mask.png",
|
|
52
|
+
quality: "auto",
|
|
53
|
+
syncMode: true,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
console.log(preview.endpoint);
|
|
57
|
+
console.log(preview.body);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Queue, Upload, and Cleanup
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
const job = await motif.submitGeneration({
|
|
64
|
+
model: "gpt2",
|
|
65
|
+
prompt: "gallery poster",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (job.isOk()) {
|
|
69
|
+
const status = await motif.getJobStatus(job.value.endpoint, job.value.requestId);
|
|
70
|
+
const completed = await motif.getJobResult(job.value.endpoint, job.value.requestId);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const uploaded = await motif.uploadToFalCdn(fileBytes, {
|
|
74
|
+
contentType: "image/png",
|
|
75
|
+
fileName: "reference.png",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const deleted = await motif.deletePayloads("fal-request-id");
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Utility Tools and Video
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
const mask = await motif.runTool({
|
|
85
|
+
tool: "sam3-image",
|
|
86
|
+
input: "https://example.com/input.png",
|
|
87
|
+
options: { prompt: "shoe", max_masks: 2 },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const videoJob = await motif.submitVideo({
|
|
91
|
+
imageUrl: "https://example.com/frame.png",
|
|
92
|
+
prompt: "slow cinematic push-in",
|
|
93
|
+
duration: 5,
|
|
94
|
+
generateAudio: false,
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Main Exports
|
|
99
|
+
|
|
100
|
+
- `MotifServer` - Result-returning fal client for generation, queue jobs, upload, utility tools, and payload deletion.
|
|
101
|
+
- `buildGenerateBody` - Pure fal request normalization for dry runs and tests.
|
|
102
|
+
- `MODELS`, `GENERATION_MODELS`, `UTILITY_MODELS`, `VIDEO_MODELS` - Motif model aliases, fal endpoints, capabilities, pricing, and benchmarks.
|
|
103
|
+
- `FAL_TOOLS`, `FAL_TOOL_IDS`, `buildFalToolRequest`, `isFalToolId` - Normalized fal utility endpoints such as SAM, depth, upscaling, moderation, and background removal.
|
|
104
|
+
- `ASPECT_RATIOS`, `RESOLUTIONS`, `FORMAT_PRESETS`, `aspectToGptSize`, `aspectToFalImageSize` - Shared sizing metadata and normalization helpers.
|
|
105
|
+
- `IMAGE_TEXT_TO_IMAGE_TOP_20`, `IMAGE_EDITING_TOP_20`, `VIDEO_TEXT_TO_VIDEO_TOP_15`, `VIDEO_IMAGE_TO_VIDEO_TOP_15` - Bundled Artificial Analysis snapshots.
|
|
106
|
+
- `estimateCost`, `estimateVideoCost` - Local cost estimates used by CLI dry runs and SDK previews.
|
|
107
|
+
- `getFalKeyFromEnv` - `@howells/envy` backed `FAL_KEY` parsing.
|
|
108
|
+
- Re-exported `neverthrow` helpers: `ok`, `err`, `Result`, `ResultAsync`.
|
|
109
|
+
|
|
110
|
+
## Common Types
|
|
111
|
+
|
|
112
|
+
The package exports public types for generation, processing, queue, metadata, and utility tools:
|
|
113
|
+
|
|
114
|
+
- `GenerateOptions`, `MotifResponse`, `MotifImage`
|
|
115
|
+
- `UpscaleOptions`, `RemoveBackgroundOptions`, `VideoOptions`, `VideoResponse`
|
|
116
|
+
- `QueuedJob`, `JobStatus`, `MotifServerConfig`
|
|
117
|
+
- `ModelConfig`, `AspectRatio`, `Resolution`, `ImageSize`, `ImageQuality`, `BackgroundMode`, `ThinkingLevel`
|
|
118
|
+
- `FalToolConfig`, `FalToolId`, `FalToolRequest`, `FalToolRunOptions`
|
|
119
|
+
|
|
120
|
+
## Testing
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
pnpm --filter @howells/motif-sdk test
|
|
124
|
+
pnpm --filter @howells/motif-sdk typecheck
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Live fal canaries are opt-in:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
RUN_FAL_CANARY=1 pnpm --filter @howells/motif-sdk test -- tests/fal-canary.test.ts
|
|
131
|
+
```
|
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
ASPECT_RATIOS: () => ASPECT_RATIOS,
|
|
24
|
+
CREATIVE_FIELDS: () => CREATIVE_FIELDS,
|
|
25
|
+
CREATIVE_TAXONOMY: () => CREATIVE_TAXONOMY,
|
|
26
|
+
CreativeOptionError: () => CreativeOptionError,
|
|
24
27
|
FAL_TOOLS: () => FAL_TOOLS,
|
|
25
28
|
FAL_TOOLS_CHECKED_AT: () => FAL_TOOLS_CHECKED_AT,
|
|
26
29
|
FAL_TOOL_IDS: () => FAL_TOOL_IDS,
|
|
@@ -42,6 +45,7 @@ __export(index_exports, {
|
|
|
42
45
|
aspectToGptSize: () => aspectToGptSize,
|
|
43
46
|
buildFalToolRequest: () => buildFalToolRequest,
|
|
44
47
|
buildGenerateBody: () => buildGenerateBody,
|
|
48
|
+
enrichPrompt: () => enrichPrompt,
|
|
45
49
|
err: () => import_neverthrow2.err,
|
|
46
50
|
estimateCost: () => estimateCost,
|
|
47
51
|
estimateVideoCost: () => estimateVideoCost,
|
|
@@ -49,7 +53,8 @@ __export(index_exports, {
|
|
|
49
53
|
isFalToolId: () => isFalToolId,
|
|
50
54
|
motifEnvSchema: () => motifEnvSchema,
|
|
51
55
|
ok: () => import_neverthrow2.ok,
|
|
52
|
-
parseMotifEnv: () => parseMotifEnv
|
|
56
|
+
parseMotifEnv: () => parseMotifEnv,
|
|
57
|
+
sanitizePrompt: () => sanitizePrompt
|
|
53
58
|
});
|
|
54
59
|
module.exports = __toCommonJS(index_exports);
|
|
55
60
|
var import_neverthrow2 = require("neverthrow");
|
|
@@ -969,6 +974,136 @@ function estimateVideoCost(durationSeconds = 5, generateAudio = true) {
|
|
|
969
974
|
return perSecond * durationSeconds;
|
|
970
975
|
}
|
|
971
976
|
|
|
977
|
+
// src/creative.ts
|
|
978
|
+
var CONTROL_CHAR_REGEX = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
|
|
979
|
+
var CreativeOptionError = class extends Error {
|
|
980
|
+
availableIds;
|
|
981
|
+
code = "INVALID_OPTION";
|
|
982
|
+
field;
|
|
983
|
+
value;
|
|
984
|
+
constructor(details) {
|
|
985
|
+
super(
|
|
986
|
+
`Unknown creative ${details.field}: ${details.value}. Available: ${details.availableIds.join(", ")}`
|
|
987
|
+
);
|
|
988
|
+
this.name = "CreativeOptionError";
|
|
989
|
+
this.availableIds = details.availableIds;
|
|
990
|
+
this.field = details.field;
|
|
991
|
+
this.value = details.value;
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
var CREATIVE_FIELDS = [
|
|
995
|
+
"recipe",
|
|
996
|
+
"shot",
|
|
997
|
+
"lighting",
|
|
998
|
+
"genre",
|
|
999
|
+
"camera",
|
|
1000
|
+
"color",
|
|
1001
|
+
"material",
|
|
1002
|
+
"motion"
|
|
1003
|
+
];
|
|
1004
|
+
var CREATIVE_TAXONOMY = {
|
|
1005
|
+
recipe: [
|
|
1006
|
+
{
|
|
1007
|
+
clause: "cinematic scene",
|
|
1008
|
+
description: "Frames the prompt as a cinematic still or scene.",
|
|
1009
|
+
id: "cinematic",
|
|
1010
|
+
label: "Cinematic"
|
|
1011
|
+
}
|
|
1012
|
+
],
|
|
1013
|
+
shot: [
|
|
1014
|
+
{
|
|
1015
|
+
clause: "close-up composition with controlled depth of field",
|
|
1016
|
+
description: "Tight framing that emphasizes subject detail.",
|
|
1017
|
+
id: "close-up",
|
|
1018
|
+
label: "Close-up"
|
|
1019
|
+
}
|
|
1020
|
+
],
|
|
1021
|
+
lighting: [
|
|
1022
|
+
{
|
|
1023
|
+
clause: "rim lighting with defined edge highlights",
|
|
1024
|
+
description: "Back or side light that separates the subject edge.",
|
|
1025
|
+
id: "rim",
|
|
1026
|
+
label: "Rim"
|
|
1027
|
+
}
|
|
1028
|
+
],
|
|
1029
|
+
genre: [
|
|
1030
|
+
{
|
|
1031
|
+
clause: "film noir mood with high contrast shadows",
|
|
1032
|
+
description: "High-contrast cinematic mood with shadow-forward drama.",
|
|
1033
|
+
id: "film-noir",
|
|
1034
|
+
label: "Film noir"
|
|
1035
|
+
}
|
|
1036
|
+
],
|
|
1037
|
+
camera: [
|
|
1038
|
+
{
|
|
1039
|
+
clause: "macro product photography with crisp surface detail",
|
|
1040
|
+
description: "Product-oriented macro camera language for close detail.",
|
|
1041
|
+
id: "macro-product",
|
|
1042
|
+
label: "Macro product"
|
|
1043
|
+
}
|
|
1044
|
+
],
|
|
1045
|
+
color: [
|
|
1046
|
+
{
|
|
1047
|
+
clause: "monochrome palette with tonal contrast",
|
|
1048
|
+
description: "Black-and-white or single-channel tonal treatment.",
|
|
1049
|
+
id: "monochrome",
|
|
1050
|
+
label: "Monochrome"
|
|
1051
|
+
}
|
|
1052
|
+
],
|
|
1053
|
+
material: [
|
|
1054
|
+
{
|
|
1055
|
+
clause: "reflective material surfaces with controlled highlights",
|
|
1056
|
+
description: "Emphasizes reflections and highlight control on surfaces.",
|
|
1057
|
+
id: "reflective",
|
|
1058
|
+
label: "Reflective"
|
|
1059
|
+
}
|
|
1060
|
+
],
|
|
1061
|
+
motion: [
|
|
1062
|
+
{
|
|
1063
|
+
clause: "still composition with no motion blur",
|
|
1064
|
+
description: "Freezes the subject without implied movement.",
|
|
1065
|
+
id: "still",
|
|
1066
|
+
label: "Still"
|
|
1067
|
+
}
|
|
1068
|
+
]
|
|
1069
|
+
};
|
|
1070
|
+
function sanitizePrompt(prompt) {
|
|
1071
|
+
return prompt.replace(CONTROL_CHAR_REGEX, "").replace(/\r\n/g, "\n").trim();
|
|
1072
|
+
}
|
|
1073
|
+
function enrichPrompt(options) {
|
|
1074
|
+
const basePrompt = sanitizePrompt(options.prompt);
|
|
1075
|
+
const clauses = [];
|
|
1076
|
+
const selected = {};
|
|
1077
|
+
for (const field of CREATIVE_FIELDS) {
|
|
1078
|
+
const optionId = options.creative?.[field];
|
|
1079
|
+
if (!optionId) {
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
const option = CREATIVE_TAXONOMY[field].find(
|
|
1083
|
+
(candidate) => candidate.id === optionId
|
|
1084
|
+
);
|
|
1085
|
+
if (!option) {
|
|
1086
|
+
throw new CreativeOptionError({
|
|
1087
|
+
availableIds: CREATIVE_TAXONOMY[field].map((candidate) => candidate.id),
|
|
1088
|
+
field,
|
|
1089
|
+
value: optionId
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
selected[field] = option.id;
|
|
1093
|
+
if (!clauses.includes(option.clause)) {
|
|
1094
|
+
clauses.push(option.clause);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return {
|
|
1098
|
+
basePrompt,
|
|
1099
|
+
creative: {
|
|
1100
|
+
clauses,
|
|
1101
|
+
selected
|
|
1102
|
+
},
|
|
1103
|
+
prompt: clauses.length ? [basePrompt, ...clauses].join(", ") : basePrompt
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
|
|
972
1107
|
// src/env.ts
|
|
973
1108
|
var import_envy = require("@howells/envy");
|
|
974
1109
|
var import_zod = require("zod");
|
|
@@ -1135,6 +1270,7 @@ function buildGenerateBody(options) {
|
|
|
1135
1270
|
const {
|
|
1136
1271
|
prompt,
|
|
1137
1272
|
model,
|
|
1273
|
+
creative,
|
|
1138
1274
|
aspect = "1:1",
|
|
1139
1275
|
resolution = "2K",
|
|
1140
1276
|
numImages = 1,
|
|
@@ -1169,7 +1305,8 @@ function buildGenerateBody(options) {
|
|
|
1169
1305
|
throw new Error(`Unknown model: ${model}`);
|
|
1170
1306
|
}
|
|
1171
1307
|
let endpoint = config.endpoint;
|
|
1172
|
-
const
|
|
1308
|
+
const enrichedPrompt = creative ? enrichPrompt({ prompt, creative }).prompt : prompt;
|
|
1309
|
+
const body = { prompt: enrichedPrompt };
|
|
1173
1310
|
const hasEditImages = Boolean(editImageUrls?.length);
|
|
1174
1311
|
const editImages = editImageUrls ?? [];
|
|
1175
1312
|
const explicitAspect = options.aspect !== void 0;
|
|
@@ -2115,6 +2252,23 @@ var FAL_TOOLS = {
|
|
|
2115
2252
|
output_format: "png"
|
|
2116
2253
|
}
|
|
2117
2254
|
},
|
|
2255
|
+
"sam3-1-image": {
|
|
2256
|
+
name: "SAM 3.1 Image",
|
|
2257
|
+
endpoint: "fal-ai/sam-3-1/image",
|
|
2258
|
+
task: "promptable image segmentation",
|
|
2259
|
+
category: "segmentation",
|
|
2260
|
+
description: "Segment image objects with text, point, or box prompts. SAM 3.1 adds Object Multiplex for faster multi-object tracking.",
|
|
2261
|
+
inputKind: "image",
|
|
2262
|
+
inputField: "image_url",
|
|
2263
|
+
outputKeys: ["image", "masks", "metadata", "scores", "boxes"],
|
|
2264
|
+
pricing: "$0.005/request",
|
|
2265
|
+
sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/image",
|
|
2266
|
+
defaultOptions: {
|
|
2267
|
+
apply_mask: true,
|
|
2268
|
+
max_masks: 3,
|
|
2269
|
+
output_format: "png"
|
|
2270
|
+
}
|
|
2271
|
+
},
|
|
2118
2272
|
"sam3-image-rle": {
|
|
2119
2273
|
name: "SAM 3 Image RLE",
|
|
2120
2274
|
endpoint: "fal-ai/sam-3/image-rle",
|
|
@@ -2261,6 +2415,7 @@ function buildFalToolRequest(options) {
|
|
|
2261
2415
|
// src/server.ts
|
|
2262
2416
|
var FAL_BASE_URL = "https://fal.run";
|
|
2263
2417
|
var FAL_QUEUE_URL = "https://queue.fal.run";
|
|
2418
|
+
var FAL_API_URL = "https://api.fal.ai";
|
|
2264
2419
|
var FAL_REST_URL = "https://rest.alpha.fal.ai";
|
|
2265
2420
|
function endpointFromQueueUrl(url, fallback) {
|
|
2266
2421
|
if (!url) return fallback;
|
|
@@ -2300,7 +2455,8 @@ var MotifServer = class {
|
|
|
2300
2455
|
const { endpoint, body } = buildGenerateBody(options);
|
|
2301
2456
|
const response = await this.request(`${FAL_BASE_URL}/${endpoint}`, {
|
|
2302
2457
|
method: "POST",
|
|
2303
|
-
body: JSON.stringify(body)
|
|
2458
|
+
body: JSON.stringify(body),
|
|
2459
|
+
headers: this.ephemeralHeaders(options)
|
|
2304
2460
|
});
|
|
2305
2461
|
if (response.isErr()) {
|
|
2306
2462
|
return (0, import_neverthrow.err)(response.error);
|
|
@@ -2341,7 +2497,8 @@ var MotifServer = class {
|
|
|
2341
2497
|
const { endpoint, body } = buildGenerateBody(options);
|
|
2342
2498
|
const response = await this.request(`${FAL_QUEUE_URL}/${endpoint}`, {
|
|
2343
2499
|
method: "POST",
|
|
2344
|
-
body: JSON.stringify(body)
|
|
2500
|
+
body: JSON.stringify(body),
|
|
2501
|
+
headers: this.ephemeralHeaders(options)
|
|
2345
2502
|
});
|
|
2346
2503
|
if (response.isErr()) {
|
|
2347
2504
|
return (0, import_neverthrow.err)(response.error);
|
|
@@ -2392,7 +2549,7 @@ var MotifServer = class {
|
|
|
2392
2549
|
return (0, import_neverthrow.err)(response.error);
|
|
2393
2550
|
}
|
|
2394
2551
|
const data = await response.value.json();
|
|
2395
|
-
return this.normalizeResponse(data);
|
|
2552
|
+
return this.normalizeResponse(data, requestId);
|
|
2396
2553
|
}
|
|
2397
2554
|
/** ─── Processing ──────────────────────────────────────────── */
|
|
2398
2555
|
/** Upscale an image using clarity or crystal upscaler. */
|
|
@@ -2607,6 +2764,23 @@ var MotifServer = class {
|
|
|
2607
2764
|
}
|
|
2608
2765
|
return (0, import_neverthrow.ok)(await response.value.json());
|
|
2609
2766
|
}
|
|
2767
|
+
/**
|
|
2768
|
+
* Delete fal's stored IO payloads for a completed request.
|
|
2769
|
+
*
|
|
2770
|
+
* This removes request input/output payload files exposed by fal's payloads
|
|
2771
|
+
* API. It does not remove billing/account metadata or input files separately
|
|
2772
|
+
* uploaded to fal storage before a request.
|
|
2773
|
+
*/
|
|
2774
|
+
async deletePayloads(requestId) {
|
|
2775
|
+
const response = await this.request(
|
|
2776
|
+
`${FAL_API_URL}/v1/models/requests/${encodeURIComponent(requestId)}/payloads`,
|
|
2777
|
+
{ method: "DELETE" }
|
|
2778
|
+
);
|
|
2779
|
+
if (response.isErr()) {
|
|
2780
|
+
return (0, import_neverthrow.err)(response.error);
|
|
2781
|
+
}
|
|
2782
|
+
return (0, import_neverthrow.ok)(void 0);
|
|
2783
|
+
}
|
|
2610
2784
|
/** Estimate cost for a generation (no API call). */
|
|
2611
2785
|
estimateCost(model, resolution, numImages) {
|
|
2612
2786
|
return estimateCost(model, resolution, numImages);
|
|
@@ -2681,12 +2855,16 @@ var MotifServer = class {
|
|
|
2681
2855
|
}
|
|
2682
2856
|
return (0, import_neverthrow.err)(lastError ?? new MotifError("Request failed after retries", 0));
|
|
2683
2857
|
}
|
|
2858
|
+
ephemeralHeaders(options) {
|
|
2859
|
+
return options.ephemeral ? { "X-Fal-Store-IO": "0" } : {};
|
|
2860
|
+
}
|
|
2684
2861
|
/**
|
|
2685
2862
|
* Normalize fal.ai responses.
|
|
2686
2863
|
* Some APIs return `{ image: {...} }` instead of `{ images: [...] }`.
|
|
2687
2864
|
*/
|
|
2688
|
-
normalizeResponse(data) {
|
|
2865
|
+
normalizeResponse(data, fallbackRequestId) {
|
|
2689
2866
|
const obj = data;
|
|
2867
|
+
const requestId = obj.request_id ?? obj.requestId ?? fallbackRequestId;
|
|
2690
2868
|
if ("detail" in obj) {
|
|
2691
2869
|
return (0, import_neverthrow.err)(
|
|
2692
2870
|
new MotifError(obj.detail, 0, "FAL_ERROR")
|
|
@@ -2696,10 +2874,14 @@ var MotifServer = class {
|
|
|
2696
2874
|
return (0, import_neverthrow.ok)({
|
|
2697
2875
|
images: [obj.image],
|
|
2698
2876
|
seed: obj.seed,
|
|
2699
|
-
prompt: obj.prompt
|
|
2877
|
+
prompt: obj.prompt,
|
|
2878
|
+
requestId
|
|
2700
2879
|
});
|
|
2701
2880
|
}
|
|
2702
|
-
return (0, import_neverthrow.ok)(
|
|
2881
|
+
return (0, import_neverthrow.ok)({
|
|
2882
|
+
...obj,
|
|
2883
|
+
requestId
|
|
2884
|
+
});
|
|
2703
2885
|
}
|
|
2704
2886
|
};
|
|
2705
2887
|
var MotifError = class extends Error {
|
|
@@ -2715,6 +2897,9 @@ var MotifError = class extends Error {
|
|
|
2715
2897
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2716
2898
|
0 && (module.exports = {
|
|
2717
2899
|
ASPECT_RATIOS,
|
|
2900
|
+
CREATIVE_FIELDS,
|
|
2901
|
+
CREATIVE_TAXONOMY,
|
|
2902
|
+
CreativeOptionError,
|
|
2718
2903
|
FAL_TOOLS,
|
|
2719
2904
|
FAL_TOOLS_CHECKED_AT,
|
|
2720
2905
|
FAL_TOOL_IDS,
|
|
@@ -2736,6 +2921,7 @@ var MotifError = class extends Error {
|
|
|
2736
2921
|
aspectToGptSize,
|
|
2737
2922
|
buildFalToolRequest,
|
|
2738
2923
|
buildGenerateBody,
|
|
2924
|
+
enrichPrompt,
|
|
2739
2925
|
err,
|
|
2740
2926
|
estimateCost,
|
|
2741
2927
|
estimateVideoCost,
|
|
@@ -2743,5 +2929,6 @@ var MotifError = class extends Error {
|
|
|
2743
2929
|
isFalToolId,
|
|
2744
2930
|
motifEnvSchema,
|
|
2745
2931
|
ok,
|
|
2746
|
-
parseMotifEnv
|
|
2932
|
+
parseMotifEnv,
|
|
2933
|
+
sanitizePrompt
|
|
2747
2934
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -3,6 +3,143 @@ export { Result, ResultAsync, err, ok } from 'neverthrow';
|
|
|
3
3
|
import * as _howells_envy from '@howells/envy';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Creative direction fields applied to prompts in Motif's canonical order.
|
|
8
|
+
*
|
|
9
|
+
* The order matters when multiple fields are selected because prompt clauses
|
|
10
|
+
* are appended in this sequence for stable dry runs, tests, and history.
|
|
11
|
+
*/
|
|
12
|
+
type CreativeField = "recipe" | "shot" | "lighting" | "genre" | "camera" | "color" | "material" | "motion";
|
|
13
|
+
/**
|
|
14
|
+
* Selected creative option ids keyed by direction field.
|
|
15
|
+
*
|
|
16
|
+
* Values must match option ids from `CREATIVE_TAXONOMY`; unknown ids throw a
|
|
17
|
+
* `CreativeOptionError` before any fal request body is built.
|
|
18
|
+
*/
|
|
19
|
+
type CreativeDirection = Partial<Record<CreativeField, string>>;
|
|
20
|
+
/** A single selectable creative direction option exposed to CLI and MCP schemas. */
|
|
21
|
+
interface CreativeOption {
|
|
22
|
+
/** Prompt fragment appended when this option is selected. */
|
|
23
|
+
clause: string;
|
|
24
|
+
/** Human-facing explanation used in schema metadata and generated docs. */
|
|
25
|
+
description: string;
|
|
26
|
+
/** Stable machine id accepted by `CreativeDirection`. */
|
|
27
|
+
id: string;
|
|
28
|
+
/** Short display label for UIs and schema enum descriptions. */
|
|
29
|
+
label: string;
|
|
30
|
+
}
|
|
31
|
+
/** Structured details returned when a creative direction id is not recognized. */
|
|
32
|
+
interface CreativeOptionErrorDetails {
|
|
33
|
+
availableIds: string[];
|
|
34
|
+
code: "INVALID_OPTION";
|
|
35
|
+
field: CreativeField;
|
|
36
|
+
value: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Error thrown when prompt enrichment receives an unknown creative option id.
|
|
40
|
+
*
|
|
41
|
+
* The extra fields make CLI and agent callers able to show field-specific
|
|
42
|
+
* recovery hints without parsing the error message.
|
|
43
|
+
*/
|
|
44
|
+
declare class CreativeOptionError extends Error implements CreativeOptionErrorDetails {
|
|
45
|
+
readonly availableIds: string[];
|
|
46
|
+
readonly code = "INVALID_OPTION";
|
|
47
|
+
readonly field: CreativeField;
|
|
48
|
+
readonly value: string;
|
|
49
|
+
constructor(details: Omit<CreativeOptionErrorDetails, "code">);
|
|
50
|
+
}
|
|
51
|
+
/** Canonical creative field order used for prompt enrichment and schema output. */
|
|
52
|
+
declare const CREATIVE_FIELDS: readonly ["recipe", "shot", "lighting", "genre", "camera", "color", "material", "motion"];
|
|
53
|
+
/**
|
|
54
|
+
* Built-in creative direction catalog.
|
|
55
|
+
*
|
|
56
|
+
* Each field contains the option ids accepted by `CreativeDirection` and the
|
|
57
|
+
* exact prompt clause that will be appended when selected.
|
|
58
|
+
*/
|
|
59
|
+
declare const CREATIVE_TAXONOMY: {
|
|
60
|
+
readonly recipe: readonly [{
|
|
61
|
+
readonly clause: "cinematic scene";
|
|
62
|
+
readonly description: "Frames the prompt as a cinematic still or scene.";
|
|
63
|
+
readonly id: "cinematic";
|
|
64
|
+
readonly label: "Cinematic";
|
|
65
|
+
}];
|
|
66
|
+
readonly shot: readonly [{
|
|
67
|
+
readonly clause: "close-up composition with controlled depth of field";
|
|
68
|
+
readonly description: "Tight framing that emphasizes subject detail.";
|
|
69
|
+
readonly id: "close-up";
|
|
70
|
+
readonly label: "Close-up";
|
|
71
|
+
}];
|
|
72
|
+
readonly lighting: readonly [{
|
|
73
|
+
readonly clause: "rim lighting with defined edge highlights";
|
|
74
|
+
readonly description: "Back or side light that separates the subject edge.";
|
|
75
|
+
readonly id: "rim";
|
|
76
|
+
readonly label: "Rim";
|
|
77
|
+
}];
|
|
78
|
+
readonly genre: readonly [{
|
|
79
|
+
readonly clause: "film noir mood with high contrast shadows";
|
|
80
|
+
readonly description: "High-contrast cinematic mood with shadow-forward drama.";
|
|
81
|
+
readonly id: "film-noir";
|
|
82
|
+
readonly label: "Film noir";
|
|
83
|
+
}];
|
|
84
|
+
readonly camera: readonly [{
|
|
85
|
+
readonly clause: "macro product photography with crisp surface detail";
|
|
86
|
+
readonly description: "Product-oriented macro camera language for close detail.";
|
|
87
|
+
readonly id: "macro-product";
|
|
88
|
+
readonly label: "Macro product";
|
|
89
|
+
}];
|
|
90
|
+
readonly color: readonly [{
|
|
91
|
+
readonly clause: "monochrome palette with tonal contrast";
|
|
92
|
+
readonly description: "Black-and-white or single-channel tonal treatment.";
|
|
93
|
+
readonly id: "monochrome";
|
|
94
|
+
readonly label: "Monochrome";
|
|
95
|
+
}];
|
|
96
|
+
readonly material: readonly [{
|
|
97
|
+
readonly clause: "reflective material surfaces with controlled highlights";
|
|
98
|
+
readonly description: "Emphasizes reflections and highlight control on surfaces.";
|
|
99
|
+
readonly id: "reflective";
|
|
100
|
+
readonly label: "Reflective";
|
|
101
|
+
}];
|
|
102
|
+
readonly motion: readonly [{
|
|
103
|
+
readonly clause: "still composition with no motion blur";
|
|
104
|
+
readonly description: "Freezes the subject without implied movement.";
|
|
105
|
+
readonly id: "still";
|
|
106
|
+
readonly label: "Still";
|
|
107
|
+
}];
|
|
108
|
+
};
|
|
109
|
+
/** Result of applying creative direction to a base prompt. */
|
|
110
|
+
interface CreativePromptResult {
|
|
111
|
+
/** Sanitized user prompt before Motif adds creative clauses. */
|
|
112
|
+
basePrompt: string;
|
|
113
|
+
creative: {
|
|
114
|
+
/** Clauses appended to the prompt, de-duplicated in canonical field order. */
|
|
115
|
+
clauses: string[];
|
|
116
|
+
/** Validated option ids that were applied. */
|
|
117
|
+
selected: CreativeDirection;
|
|
118
|
+
};
|
|
119
|
+
/** Final prompt sent to fal after creative enrichment. */
|
|
120
|
+
prompt: string;
|
|
121
|
+
}
|
|
122
|
+
/** Input for Motif's prompt enrichment step. */
|
|
123
|
+
interface EnrichPromptOptions {
|
|
124
|
+
/** Optional selected creative option ids. */
|
|
125
|
+
creative?: CreativeDirection;
|
|
126
|
+
/** User-authored prompt before Motif normalization and enrichment. */
|
|
127
|
+
prompt: string;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Remove control characters and surrounding whitespace from a prompt.
|
|
131
|
+
*
|
|
132
|
+
* Newline style is normalized to `\n`; other text content is left unchanged.
|
|
133
|
+
*/
|
|
134
|
+
declare function sanitizePrompt(prompt: string): string;
|
|
135
|
+
/**
|
|
136
|
+
* Append selected creative direction clauses to a prompt.
|
|
137
|
+
*
|
|
138
|
+
* Options are validated against `CREATIVE_TAXONOMY`, applied in
|
|
139
|
+
* `CREATIVE_FIELDS` order, and returned as metadata alongside the final prompt.
|
|
140
|
+
*/
|
|
141
|
+
declare function enrichPrompt(options: EnrichPromptOptions): CreativePromptResult;
|
|
142
|
+
|
|
6
143
|
/** ─── Model Types ─────────────────────────────────────────────── */
|
|
7
144
|
type AspectRatio = "auto" | "8:1" | "4:1" | "21:9" | "16:9" | "3:2" | "4:3" | "5:4" | "1:1" | "4:5" | "3:4" | "2:3" | "9:16" | "1:4" | "1:8";
|
|
8
145
|
type Resolution = "0.5K" | "1K" | "2K" | "4K";
|
|
@@ -104,7 +241,16 @@ interface GenerateOptions {
|
|
|
104
241
|
aspect?: AspectRatio;
|
|
105
242
|
/** GPT background mode where supported */
|
|
106
243
|
background?: BackgroundMode;
|
|
244
|
+
/**
|
|
245
|
+
* Motif prompt enrichment choices applied before fal request construction.
|
|
246
|
+
*
|
|
247
|
+
* These options are not sent to fal directly; they append validated creative
|
|
248
|
+
* clauses to `prompt` and are omitted from the final request body.
|
|
249
|
+
*/
|
|
250
|
+
creative?: CreativeDirection;
|
|
107
251
|
editImageUrls?: string[];
|
|
252
|
+
/** Ask fal not to store IO payloads, and expose request ids for deletion. */
|
|
253
|
+
ephemeral?: boolean;
|
|
108
254
|
/** Google-search alias for fal models that expose enable_google_search */
|
|
109
255
|
enableGoogleSearch?: boolean;
|
|
110
256
|
/** fal safety checker toggle where supported */
|
|
@@ -223,6 +369,7 @@ interface MotifImage {
|
|
|
223
369
|
interface MotifResponse {
|
|
224
370
|
images: MotifImage[];
|
|
225
371
|
prompt?: string;
|
|
372
|
+
requestId?: string;
|
|
226
373
|
seed?: number;
|
|
227
374
|
}
|
|
228
375
|
/** ─── Queue Types ────────────────────────────────────────────── */
|
|
@@ -387,6 +534,14 @@ declare class MotifServer {
|
|
|
387
534
|
/** ─── Utilities ───────────────────────────────────────────── */
|
|
388
535
|
/** Run a registered fal utility/tool endpoint. */
|
|
389
536
|
runTool(options: ToolRunOptions): Promise<Result<ToolResponse, MotifError>>;
|
|
537
|
+
/**
|
|
538
|
+
* Delete fal's stored IO payloads for a completed request.
|
|
539
|
+
*
|
|
540
|
+
* This removes request input/output payload files exposed by fal's payloads
|
|
541
|
+
* API. It does not remove billing/account metadata or input files separately
|
|
542
|
+
* uploaded to fal storage before a request.
|
|
543
|
+
*/
|
|
544
|
+
deletePayloads(requestId: string): Promise<Result<void, MotifError>>;
|
|
390
545
|
/** Estimate cost for a generation (no API call). */
|
|
391
546
|
estimateCost(model: string, resolution?: Resolution, numImages?: number): number;
|
|
392
547
|
/** Build the fal.ai request body without sending it. */
|
|
@@ -628,6 +783,23 @@ declare class MotifServer {
|
|
|
628
783
|
readonly output_format: "png";
|
|
629
784
|
};
|
|
630
785
|
};
|
|
786
|
+
readonly "sam3-1-image": {
|
|
787
|
+
readonly name: "SAM 3.1 Image";
|
|
788
|
+
readonly endpoint: "fal-ai/sam-3-1/image";
|
|
789
|
+
readonly task: "promptable image segmentation";
|
|
790
|
+
readonly category: "segmentation";
|
|
791
|
+
readonly description: "Segment image objects with text, point, or box prompts. SAM 3.1 adds Object Multiplex for faster multi-object tracking.";
|
|
792
|
+
readonly inputKind: "image";
|
|
793
|
+
readonly inputField: "image_url";
|
|
794
|
+
readonly outputKeys: ["image", "masks", "metadata", "scores", "boxes"];
|
|
795
|
+
readonly pricing: "$0.005/request";
|
|
796
|
+
readonly sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/image";
|
|
797
|
+
readonly defaultOptions: {
|
|
798
|
+
readonly apply_mask: true;
|
|
799
|
+
readonly max_masks: 3;
|
|
800
|
+
readonly output_format: "png";
|
|
801
|
+
};
|
|
802
|
+
};
|
|
631
803
|
readonly "sam3-image-rle": {
|
|
632
804
|
readonly name: "SAM 3 Image RLE";
|
|
633
805
|
readonly endpoint: "fal-ai/sam-3/image-rle";
|
|
@@ -743,6 +915,7 @@ declare class MotifServer {
|
|
|
743
915
|
/** ─── Private ─────────────────────────────────────────────── */
|
|
744
916
|
/** Authenticated fetch to fal.ai APIs with retry logic. */
|
|
745
917
|
private request;
|
|
918
|
+
private ephemeralHeaders;
|
|
746
919
|
/**
|
|
747
920
|
* Normalize fal.ai responses.
|
|
748
921
|
* Some APIs return `{ image: {...} }` instead of `{ images: [...] }`.
|
|
@@ -1007,6 +1180,23 @@ declare const FAL_TOOLS: {
|
|
|
1007
1180
|
readonly output_format: "png";
|
|
1008
1181
|
};
|
|
1009
1182
|
};
|
|
1183
|
+
readonly "sam3-1-image": {
|
|
1184
|
+
readonly name: "SAM 3.1 Image";
|
|
1185
|
+
readonly endpoint: "fal-ai/sam-3-1/image";
|
|
1186
|
+
readonly task: "promptable image segmentation";
|
|
1187
|
+
readonly category: "segmentation";
|
|
1188
|
+
readonly description: "Segment image objects with text, point, or box prompts. SAM 3.1 adds Object Multiplex for faster multi-object tracking.";
|
|
1189
|
+
readonly inputKind: "image";
|
|
1190
|
+
readonly inputField: "image_url";
|
|
1191
|
+
readonly outputKeys: ["image", "masks", "metadata", "scores", "boxes"];
|
|
1192
|
+
readonly pricing: "$0.005/request";
|
|
1193
|
+
readonly sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/image";
|
|
1194
|
+
readonly defaultOptions: {
|
|
1195
|
+
readonly apply_mask: true;
|
|
1196
|
+
readonly max_masks: 3;
|
|
1197
|
+
readonly output_format: "png";
|
|
1198
|
+
};
|
|
1199
|
+
};
|
|
1010
1200
|
readonly "sam3-image-rle": {
|
|
1011
1201
|
readonly name: "SAM 3 Image RLE";
|
|
1012
1202
|
readonly endpoint: "fal-ai/sam-3/image-rle";
|
|
@@ -1125,4 +1315,4 @@ type FalToolId = (typeof FAL_TOOL_IDS)[number];
|
|
|
1125
1315
|
declare function isFalToolId(tool: string): tool is FalToolId;
|
|
1126
1316
|
declare function buildFalToolRequest(options: FalToolRunOptions): FalToolRequest;
|
|
1127
1317
|
|
|
1128
|
-
export { ASPECT_RATIOS, type AspectRatio, type BackgroundMode, type CustomImageSize, FAL_TOOLS, FAL_TOOLS_CHECKED_AT, FAL_TOOL_IDS, FORMAT_PRESETS, type FalImageSizePreset, type FalToolConfig, type FalToolId, type FalToolInputKind, type FalToolRequest, type FalToolRunOptions, GENERATION_MODELS, type GenerateOptions, type GenerationModelName, type GptImageSize, IDEOGRAM_STYLES, IMAGE_EDITING_TOP_20, IMAGE_TEXT_TO_IMAGE_TOP_20, type ImageOutputFormat, type ImageQuality, type ImageSize, type JobStatus, type LeaderboardEntry, type LeaderboardSnapshot, MODELS, type ModelConfig, type ModelType, type MotifEnv, MotifError, type MotifImage, type MotifResponse, MotifServer, type MotifServerConfig, type QueuedJob, RECRAFT_STYLES, RESOLUTIONS, type RemoveBackgroundOptions, type Resolution, type SizeMode, type ThinkingLevel, type ToolResponse, type ToolRunOptions, UTILITY_MODELS, type UpscaleOptions, VIDEO_IMAGE_TO_VIDEO_TOP_15, VIDEO_MODELS, VIDEO_TEXT_TO_VIDEO_TOP_15, type VideoOptions, type VideoResponse, aspectToFalImageSize, aspectToGptSize, buildFalToolRequest, buildGenerateBody, estimateCost, estimateVideoCost, getFalKeyFromEnv, isFalToolId, motifEnvSchema, parseMotifEnv };
|
|
1318
|
+
export { ASPECT_RATIOS, type AspectRatio, type BackgroundMode, CREATIVE_FIELDS, CREATIVE_TAXONOMY, type CreativeDirection, type CreativeField, type CreativeOption, CreativeOptionError, type CreativeOptionErrorDetails, type CreativePromptResult, type CustomImageSize, type EnrichPromptOptions, FAL_TOOLS, FAL_TOOLS_CHECKED_AT, FAL_TOOL_IDS, FORMAT_PRESETS, type FalImageSizePreset, type FalToolConfig, type FalToolId, type FalToolInputKind, type FalToolRequest, type FalToolRunOptions, GENERATION_MODELS, type GenerateOptions, type GenerationModelName, type GptImageSize, IDEOGRAM_STYLES, IMAGE_EDITING_TOP_20, IMAGE_TEXT_TO_IMAGE_TOP_20, type ImageOutputFormat, type ImageQuality, type ImageSize, type JobStatus, type LeaderboardEntry, type LeaderboardSnapshot, MODELS, type ModelConfig, type ModelType, type MotifEnv, MotifError, type MotifImage, type MotifResponse, MotifServer, type MotifServerConfig, type QueuedJob, RECRAFT_STYLES, RESOLUTIONS, type RemoveBackgroundOptions, type Resolution, type SizeMode, type ThinkingLevel, type ToolResponse, type ToolRunOptions, UTILITY_MODELS, type UpscaleOptions, VIDEO_IMAGE_TO_VIDEO_TOP_15, VIDEO_MODELS, VIDEO_TEXT_TO_VIDEO_TOP_15, type VideoOptions, type VideoResponse, aspectToFalImageSize, aspectToGptSize, buildFalToolRequest, buildGenerateBody, enrichPrompt, estimateCost, estimateVideoCost, getFalKeyFromEnv, isFalToolId, motifEnvSchema, parseMotifEnv, sanitizePrompt };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,143 @@ export { Result, ResultAsync, err, ok } from 'neverthrow';
|
|
|
3
3
|
import * as _howells_envy from '@howells/envy';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Creative direction fields applied to prompts in Motif's canonical order.
|
|
8
|
+
*
|
|
9
|
+
* The order matters when multiple fields are selected because prompt clauses
|
|
10
|
+
* are appended in this sequence for stable dry runs, tests, and history.
|
|
11
|
+
*/
|
|
12
|
+
type CreativeField = "recipe" | "shot" | "lighting" | "genre" | "camera" | "color" | "material" | "motion";
|
|
13
|
+
/**
|
|
14
|
+
* Selected creative option ids keyed by direction field.
|
|
15
|
+
*
|
|
16
|
+
* Values must match option ids from `CREATIVE_TAXONOMY`; unknown ids throw a
|
|
17
|
+
* `CreativeOptionError` before any fal request body is built.
|
|
18
|
+
*/
|
|
19
|
+
type CreativeDirection = Partial<Record<CreativeField, string>>;
|
|
20
|
+
/** A single selectable creative direction option exposed to CLI and MCP schemas. */
|
|
21
|
+
interface CreativeOption {
|
|
22
|
+
/** Prompt fragment appended when this option is selected. */
|
|
23
|
+
clause: string;
|
|
24
|
+
/** Human-facing explanation used in schema metadata and generated docs. */
|
|
25
|
+
description: string;
|
|
26
|
+
/** Stable machine id accepted by `CreativeDirection`. */
|
|
27
|
+
id: string;
|
|
28
|
+
/** Short display label for UIs and schema enum descriptions. */
|
|
29
|
+
label: string;
|
|
30
|
+
}
|
|
31
|
+
/** Structured details returned when a creative direction id is not recognized. */
|
|
32
|
+
interface CreativeOptionErrorDetails {
|
|
33
|
+
availableIds: string[];
|
|
34
|
+
code: "INVALID_OPTION";
|
|
35
|
+
field: CreativeField;
|
|
36
|
+
value: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Error thrown when prompt enrichment receives an unknown creative option id.
|
|
40
|
+
*
|
|
41
|
+
* The extra fields make CLI and agent callers able to show field-specific
|
|
42
|
+
* recovery hints without parsing the error message.
|
|
43
|
+
*/
|
|
44
|
+
declare class CreativeOptionError extends Error implements CreativeOptionErrorDetails {
|
|
45
|
+
readonly availableIds: string[];
|
|
46
|
+
readonly code = "INVALID_OPTION";
|
|
47
|
+
readonly field: CreativeField;
|
|
48
|
+
readonly value: string;
|
|
49
|
+
constructor(details: Omit<CreativeOptionErrorDetails, "code">);
|
|
50
|
+
}
|
|
51
|
+
/** Canonical creative field order used for prompt enrichment and schema output. */
|
|
52
|
+
declare const CREATIVE_FIELDS: readonly ["recipe", "shot", "lighting", "genre", "camera", "color", "material", "motion"];
|
|
53
|
+
/**
|
|
54
|
+
* Built-in creative direction catalog.
|
|
55
|
+
*
|
|
56
|
+
* Each field contains the option ids accepted by `CreativeDirection` and the
|
|
57
|
+
* exact prompt clause that will be appended when selected.
|
|
58
|
+
*/
|
|
59
|
+
declare const CREATIVE_TAXONOMY: {
|
|
60
|
+
readonly recipe: readonly [{
|
|
61
|
+
readonly clause: "cinematic scene";
|
|
62
|
+
readonly description: "Frames the prompt as a cinematic still or scene.";
|
|
63
|
+
readonly id: "cinematic";
|
|
64
|
+
readonly label: "Cinematic";
|
|
65
|
+
}];
|
|
66
|
+
readonly shot: readonly [{
|
|
67
|
+
readonly clause: "close-up composition with controlled depth of field";
|
|
68
|
+
readonly description: "Tight framing that emphasizes subject detail.";
|
|
69
|
+
readonly id: "close-up";
|
|
70
|
+
readonly label: "Close-up";
|
|
71
|
+
}];
|
|
72
|
+
readonly lighting: readonly [{
|
|
73
|
+
readonly clause: "rim lighting with defined edge highlights";
|
|
74
|
+
readonly description: "Back or side light that separates the subject edge.";
|
|
75
|
+
readonly id: "rim";
|
|
76
|
+
readonly label: "Rim";
|
|
77
|
+
}];
|
|
78
|
+
readonly genre: readonly [{
|
|
79
|
+
readonly clause: "film noir mood with high contrast shadows";
|
|
80
|
+
readonly description: "High-contrast cinematic mood with shadow-forward drama.";
|
|
81
|
+
readonly id: "film-noir";
|
|
82
|
+
readonly label: "Film noir";
|
|
83
|
+
}];
|
|
84
|
+
readonly camera: readonly [{
|
|
85
|
+
readonly clause: "macro product photography with crisp surface detail";
|
|
86
|
+
readonly description: "Product-oriented macro camera language for close detail.";
|
|
87
|
+
readonly id: "macro-product";
|
|
88
|
+
readonly label: "Macro product";
|
|
89
|
+
}];
|
|
90
|
+
readonly color: readonly [{
|
|
91
|
+
readonly clause: "monochrome palette with tonal contrast";
|
|
92
|
+
readonly description: "Black-and-white or single-channel tonal treatment.";
|
|
93
|
+
readonly id: "monochrome";
|
|
94
|
+
readonly label: "Monochrome";
|
|
95
|
+
}];
|
|
96
|
+
readonly material: readonly [{
|
|
97
|
+
readonly clause: "reflective material surfaces with controlled highlights";
|
|
98
|
+
readonly description: "Emphasizes reflections and highlight control on surfaces.";
|
|
99
|
+
readonly id: "reflective";
|
|
100
|
+
readonly label: "Reflective";
|
|
101
|
+
}];
|
|
102
|
+
readonly motion: readonly [{
|
|
103
|
+
readonly clause: "still composition with no motion blur";
|
|
104
|
+
readonly description: "Freezes the subject without implied movement.";
|
|
105
|
+
readonly id: "still";
|
|
106
|
+
readonly label: "Still";
|
|
107
|
+
}];
|
|
108
|
+
};
|
|
109
|
+
/** Result of applying creative direction to a base prompt. */
|
|
110
|
+
interface CreativePromptResult {
|
|
111
|
+
/** Sanitized user prompt before Motif adds creative clauses. */
|
|
112
|
+
basePrompt: string;
|
|
113
|
+
creative: {
|
|
114
|
+
/** Clauses appended to the prompt, de-duplicated in canonical field order. */
|
|
115
|
+
clauses: string[];
|
|
116
|
+
/** Validated option ids that were applied. */
|
|
117
|
+
selected: CreativeDirection;
|
|
118
|
+
};
|
|
119
|
+
/** Final prompt sent to fal after creative enrichment. */
|
|
120
|
+
prompt: string;
|
|
121
|
+
}
|
|
122
|
+
/** Input for Motif's prompt enrichment step. */
|
|
123
|
+
interface EnrichPromptOptions {
|
|
124
|
+
/** Optional selected creative option ids. */
|
|
125
|
+
creative?: CreativeDirection;
|
|
126
|
+
/** User-authored prompt before Motif normalization and enrichment. */
|
|
127
|
+
prompt: string;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Remove control characters and surrounding whitespace from a prompt.
|
|
131
|
+
*
|
|
132
|
+
* Newline style is normalized to `\n`; other text content is left unchanged.
|
|
133
|
+
*/
|
|
134
|
+
declare function sanitizePrompt(prompt: string): string;
|
|
135
|
+
/**
|
|
136
|
+
* Append selected creative direction clauses to a prompt.
|
|
137
|
+
*
|
|
138
|
+
* Options are validated against `CREATIVE_TAXONOMY`, applied in
|
|
139
|
+
* `CREATIVE_FIELDS` order, and returned as metadata alongside the final prompt.
|
|
140
|
+
*/
|
|
141
|
+
declare function enrichPrompt(options: EnrichPromptOptions): CreativePromptResult;
|
|
142
|
+
|
|
6
143
|
/** ─── Model Types ─────────────────────────────────────────────── */
|
|
7
144
|
type AspectRatio = "auto" | "8:1" | "4:1" | "21:9" | "16:9" | "3:2" | "4:3" | "5:4" | "1:1" | "4:5" | "3:4" | "2:3" | "9:16" | "1:4" | "1:8";
|
|
8
145
|
type Resolution = "0.5K" | "1K" | "2K" | "4K";
|
|
@@ -104,7 +241,16 @@ interface GenerateOptions {
|
|
|
104
241
|
aspect?: AspectRatio;
|
|
105
242
|
/** GPT background mode where supported */
|
|
106
243
|
background?: BackgroundMode;
|
|
244
|
+
/**
|
|
245
|
+
* Motif prompt enrichment choices applied before fal request construction.
|
|
246
|
+
*
|
|
247
|
+
* These options are not sent to fal directly; they append validated creative
|
|
248
|
+
* clauses to `prompt` and are omitted from the final request body.
|
|
249
|
+
*/
|
|
250
|
+
creative?: CreativeDirection;
|
|
107
251
|
editImageUrls?: string[];
|
|
252
|
+
/** Ask fal not to store IO payloads, and expose request ids for deletion. */
|
|
253
|
+
ephemeral?: boolean;
|
|
108
254
|
/** Google-search alias for fal models that expose enable_google_search */
|
|
109
255
|
enableGoogleSearch?: boolean;
|
|
110
256
|
/** fal safety checker toggle where supported */
|
|
@@ -223,6 +369,7 @@ interface MotifImage {
|
|
|
223
369
|
interface MotifResponse {
|
|
224
370
|
images: MotifImage[];
|
|
225
371
|
prompt?: string;
|
|
372
|
+
requestId?: string;
|
|
226
373
|
seed?: number;
|
|
227
374
|
}
|
|
228
375
|
/** ─── Queue Types ────────────────────────────────────────────── */
|
|
@@ -387,6 +534,14 @@ declare class MotifServer {
|
|
|
387
534
|
/** ─── Utilities ───────────────────────────────────────────── */
|
|
388
535
|
/** Run a registered fal utility/tool endpoint. */
|
|
389
536
|
runTool(options: ToolRunOptions): Promise<Result<ToolResponse, MotifError>>;
|
|
537
|
+
/**
|
|
538
|
+
* Delete fal's stored IO payloads for a completed request.
|
|
539
|
+
*
|
|
540
|
+
* This removes request input/output payload files exposed by fal's payloads
|
|
541
|
+
* API. It does not remove billing/account metadata or input files separately
|
|
542
|
+
* uploaded to fal storage before a request.
|
|
543
|
+
*/
|
|
544
|
+
deletePayloads(requestId: string): Promise<Result<void, MotifError>>;
|
|
390
545
|
/** Estimate cost for a generation (no API call). */
|
|
391
546
|
estimateCost(model: string, resolution?: Resolution, numImages?: number): number;
|
|
392
547
|
/** Build the fal.ai request body without sending it. */
|
|
@@ -628,6 +783,23 @@ declare class MotifServer {
|
|
|
628
783
|
readonly output_format: "png";
|
|
629
784
|
};
|
|
630
785
|
};
|
|
786
|
+
readonly "sam3-1-image": {
|
|
787
|
+
readonly name: "SAM 3.1 Image";
|
|
788
|
+
readonly endpoint: "fal-ai/sam-3-1/image";
|
|
789
|
+
readonly task: "promptable image segmentation";
|
|
790
|
+
readonly category: "segmentation";
|
|
791
|
+
readonly description: "Segment image objects with text, point, or box prompts. SAM 3.1 adds Object Multiplex for faster multi-object tracking.";
|
|
792
|
+
readonly inputKind: "image";
|
|
793
|
+
readonly inputField: "image_url";
|
|
794
|
+
readonly outputKeys: ["image", "masks", "metadata", "scores", "boxes"];
|
|
795
|
+
readonly pricing: "$0.005/request";
|
|
796
|
+
readonly sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/image";
|
|
797
|
+
readonly defaultOptions: {
|
|
798
|
+
readonly apply_mask: true;
|
|
799
|
+
readonly max_masks: 3;
|
|
800
|
+
readonly output_format: "png";
|
|
801
|
+
};
|
|
802
|
+
};
|
|
631
803
|
readonly "sam3-image-rle": {
|
|
632
804
|
readonly name: "SAM 3 Image RLE";
|
|
633
805
|
readonly endpoint: "fal-ai/sam-3/image-rle";
|
|
@@ -743,6 +915,7 @@ declare class MotifServer {
|
|
|
743
915
|
/** ─── Private ─────────────────────────────────────────────── */
|
|
744
916
|
/** Authenticated fetch to fal.ai APIs with retry logic. */
|
|
745
917
|
private request;
|
|
918
|
+
private ephemeralHeaders;
|
|
746
919
|
/**
|
|
747
920
|
* Normalize fal.ai responses.
|
|
748
921
|
* Some APIs return `{ image: {...} }` instead of `{ images: [...] }`.
|
|
@@ -1007,6 +1180,23 @@ declare const FAL_TOOLS: {
|
|
|
1007
1180
|
readonly output_format: "png";
|
|
1008
1181
|
};
|
|
1009
1182
|
};
|
|
1183
|
+
readonly "sam3-1-image": {
|
|
1184
|
+
readonly name: "SAM 3.1 Image";
|
|
1185
|
+
readonly endpoint: "fal-ai/sam-3-1/image";
|
|
1186
|
+
readonly task: "promptable image segmentation";
|
|
1187
|
+
readonly category: "segmentation";
|
|
1188
|
+
readonly description: "Segment image objects with text, point, or box prompts. SAM 3.1 adds Object Multiplex for faster multi-object tracking.";
|
|
1189
|
+
readonly inputKind: "image";
|
|
1190
|
+
readonly inputField: "image_url";
|
|
1191
|
+
readonly outputKeys: ["image", "masks", "metadata", "scores", "boxes"];
|
|
1192
|
+
readonly pricing: "$0.005/request";
|
|
1193
|
+
readonly sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/image";
|
|
1194
|
+
readonly defaultOptions: {
|
|
1195
|
+
readonly apply_mask: true;
|
|
1196
|
+
readonly max_masks: 3;
|
|
1197
|
+
readonly output_format: "png";
|
|
1198
|
+
};
|
|
1199
|
+
};
|
|
1010
1200
|
readonly "sam3-image-rle": {
|
|
1011
1201
|
readonly name: "SAM 3 Image RLE";
|
|
1012
1202
|
readonly endpoint: "fal-ai/sam-3/image-rle";
|
|
@@ -1125,4 +1315,4 @@ type FalToolId = (typeof FAL_TOOL_IDS)[number];
|
|
|
1125
1315
|
declare function isFalToolId(tool: string): tool is FalToolId;
|
|
1126
1316
|
declare function buildFalToolRequest(options: FalToolRunOptions): FalToolRequest;
|
|
1127
1317
|
|
|
1128
|
-
export { ASPECT_RATIOS, type AspectRatio, type BackgroundMode, type CustomImageSize, FAL_TOOLS, FAL_TOOLS_CHECKED_AT, FAL_TOOL_IDS, FORMAT_PRESETS, type FalImageSizePreset, type FalToolConfig, type FalToolId, type FalToolInputKind, type FalToolRequest, type FalToolRunOptions, GENERATION_MODELS, type GenerateOptions, type GenerationModelName, type GptImageSize, IDEOGRAM_STYLES, IMAGE_EDITING_TOP_20, IMAGE_TEXT_TO_IMAGE_TOP_20, type ImageOutputFormat, type ImageQuality, type ImageSize, type JobStatus, type LeaderboardEntry, type LeaderboardSnapshot, MODELS, type ModelConfig, type ModelType, type MotifEnv, MotifError, type MotifImage, type MotifResponse, MotifServer, type MotifServerConfig, type QueuedJob, RECRAFT_STYLES, RESOLUTIONS, type RemoveBackgroundOptions, type Resolution, type SizeMode, type ThinkingLevel, type ToolResponse, type ToolRunOptions, UTILITY_MODELS, type UpscaleOptions, VIDEO_IMAGE_TO_VIDEO_TOP_15, VIDEO_MODELS, VIDEO_TEXT_TO_VIDEO_TOP_15, type VideoOptions, type VideoResponse, aspectToFalImageSize, aspectToGptSize, buildFalToolRequest, buildGenerateBody, estimateCost, estimateVideoCost, getFalKeyFromEnv, isFalToolId, motifEnvSchema, parseMotifEnv };
|
|
1318
|
+
export { ASPECT_RATIOS, type AspectRatio, type BackgroundMode, CREATIVE_FIELDS, CREATIVE_TAXONOMY, type CreativeDirection, type CreativeField, type CreativeOption, CreativeOptionError, type CreativeOptionErrorDetails, type CreativePromptResult, type CustomImageSize, type EnrichPromptOptions, FAL_TOOLS, FAL_TOOLS_CHECKED_AT, FAL_TOOL_IDS, FORMAT_PRESETS, type FalImageSizePreset, type FalToolConfig, type FalToolId, type FalToolInputKind, type FalToolRequest, type FalToolRunOptions, GENERATION_MODELS, type GenerateOptions, type GenerationModelName, type GptImageSize, IDEOGRAM_STYLES, IMAGE_EDITING_TOP_20, IMAGE_TEXT_TO_IMAGE_TOP_20, type ImageOutputFormat, type ImageQuality, type ImageSize, type JobStatus, type LeaderboardEntry, type LeaderboardSnapshot, MODELS, type ModelConfig, type ModelType, type MotifEnv, MotifError, type MotifImage, type MotifResponse, MotifServer, type MotifServerConfig, type QueuedJob, RECRAFT_STYLES, RESOLUTIONS, type RemoveBackgroundOptions, type Resolution, type SizeMode, type ThinkingLevel, type ToolResponse, type ToolRunOptions, UTILITY_MODELS, type UpscaleOptions, VIDEO_IMAGE_TO_VIDEO_TOP_15, VIDEO_MODELS, VIDEO_TEXT_TO_VIDEO_TOP_15, type VideoOptions, type VideoResponse, aspectToFalImageSize, aspectToGptSize, buildFalToolRequest, buildGenerateBody, enrichPrompt, estimateCost, estimateVideoCost, getFalKeyFromEnv, isFalToolId, motifEnvSchema, parseMotifEnv, sanitizePrompt };
|
package/dist/index.js
CHANGED
|
@@ -916,6 +916,136 @@ function estimateVideoCost(durationSeconds = 5, generateAudio = true) {
|
|
|
916
916
|
return perSecond * durationSeconds;
|
|
917
917
|
}
|
|
918
918
|
|
|
919
|
+
// src/creative.ts
|
|
920
|
+
var CONTROL_CHAR_REGEX = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
|
|
921
|
+
var CreativeOptionError = class extends Error {
|
|
922
|
+
availableIds;
|
|
923
|
+
code = "INVALID_OPTION";
|
|
924
|
+
field;
|
|
925
|
+
value;
|
|
926
|
+
constructor(details) {
|
|
927
|
+
super(
|
|
928
|
+
`Unknown creative ${details.field}: ${details.value}. Available: ${details.availableIds.join(", ")}`
|
|
929
|
+
);
|
|
930
|
+
this.name = "CreativeOptionError";
|
|
931
|
+
this.availableIds = details.availableIds;
|
|
932
|
+
this.field = details.field;
|
|
933
|
+
this.value = details.value;
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
var CREATIVE_FIELDS = [
|
|
937
|
+
"recipe",
|
|
938
|
+
"shot",
|
|
939
|
+
"lighting",
|
|
940
|
+
"genre",
|
|
941
|
+
"camera",
|
|
942
|
+
"color",
|
|
943
|
+
"material",
|
|
944
|
+
"motion"
|
|
945
|
+
];
|
|
946
|
+
var CREATIVE_TAXONOMY = {
|
|
947
|
+
recipe: [
|
|
948
|
+
{
|
|
949
|
+
clause: "cinematic scene",
|
|
950
|
+
description: "Frames the prompt as a cinematic still or scene.",
|
|
951
|
+
id: "cinematic",
|
|
952
|
+
label: "Cinematic"
|
|
953
|
+
}
|
|
954
|
+
],
|
|
955
|
+
shot: [
|
|
956
|
+
{
|
|
957
|
+
clause: "close-up composition with controlled depth of field",
|
|
958
|
+
description: "Tight framing that emphasizes subject detail.",
|
|
959
|
+
id: "close-up",
|
|
960
|
+
label: "Close-up"
|
|
961
|
+
}
|
|
962
|
+
],
|
|
963
|
+
lighting: [
|
|
964
|
+
{
|
|
965
|
+
clause: "rim lighting with defined edge highlights",
|
|
966
|
+
description: "Back or side light that separates the subject edge.",
|
|
967
|
+
id: "rim",
|
|
968
|
+
label: "Rim"
|
|
969
|
+
}
|
|
970
|
+
],
|
|
971
|
+
genre: [
|
|
972
|
+
{
|
|
973
|
+
clause: "film noir mood with high contrast shadows",
|
|
974
|
+
description: "High-contrast cinematic mood with shadow-forward drama.",
|
|
975
|
+
id: "film-noir",
|
|
976
|
+
label: "Film noir"
|
|
977
|
+
}
|
|
978
|
+
],
|
|
979
|
+
camera: [
|
|
980
|
+
{
|
|
981
|
+
clause: "macro product photography with crisp surface detail",
|
|
982
|
+
description: "Product-oriented macro camera language for close detail.",
|
|
983
|
+
id: "macro-product",
|
|
984
|
+
label: "Macro product"
|
|
985
|
+
}
|
|
986
|
+
],
|
|
987
|
+
color: [
|
|
988
|
+
{
|
|
989
|
+
clause: "monochrome palette with tonal contrast",
|
|
990
|
+
description: "Black-and-white or single-channel tonal treatment.",
|
|
991
|
+
id: "monochrome",
|
|
992
|
+
label: "Monochrome"
|
|
993
|
+
}
|
|
994
|
+
],
|
|
995
|
+
material: [
|
|
996
|
+
{
|
|
997
|
+
clause: "reflective material surfaces with controlled highlights",
|
|
998
|
+
description: "Emphasizes reflections and highlight control on surfaces.",
|
|
999
|
+
id: "reflective",
|
|
1000
|
+
label: "Reflective"
|
|
1001
|
+
}
|
|
1002
|
+
],
|
|
1003
|
+
motion: [
|
|
1004
|
+
{
|
|
1005
|
+
clause: "still composition with no motion blur",
|
|
1006
|
+
description: "Freezes the subject without implied movement.",
|
|
1007
|
+
id: "still",
|
|
1008
|
+
label: "Still"
|
|
1009
|
+
}
|
|
1010
|
+
]
|
|
1011
|
+
};
|
|
1012
|
+
function sanitizePrompt(prompt) {
|
|
1013
|
+
return prompt.replace(CONTROL_CHAR_REGEX, "").replace(/\r\n/g, "\n").trim();
|
|
1014
|
+
}
|
|
1015
|
+
function enrichPrompt(options) {
|
|
1016
|
+
const basePrompt = sanitizePrompt(options.prompt);
|
|
1017
|
+
const clauses = [];
|
|
1018
|
+
const selected = {};
|
|
1019
|
+
for (const field of CREATIVE_FIELDS) {
|
|
1020
|
+
const optionId = options.creative?.[field];
|
|
1021
|
+
if (!optionId) {
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
const option = CREATIVE_TAXONOMY[field].find(
|
|
1025
|
+
(candidate) => candidate.id === optionId
|
|
1026
|
+
);
|
|
1027
|
+
if (!option) {
|
|
1028
|
+
throw new CreativeOptionError({
|
|
1029
|
+
availableIds: CREATIVE_TAXONOMY[field].map((candidate) => candidate.id),
|
|
1030
|
+
field,
|
|
1031
|
+
value: optionId
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
selected[field] = option.id;
|
|
1035
|
+
if (!clauses.includes(option.clause)) {
|
|
1036
|
+
clauses.push(option.clause);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
return {
|
|
1040
|
+
basePrompt,
|
|
1041
|
+
creative: {
|
|
1042
|
+
clauses,
|
|
1043
|
+
selected
|
|
1044
|
+
},
|
|
1045
|
+
prompt: clauses.length ? [basePrompt, ...clauses].join(", ") : basePrompt
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
|
|
919
1049
|
// src/env.ts
|
|
920
1050
|
import { defineEnv } from "@howells/envy";
|
|
921
1051
|
import { z } from "zod";
|
|
@@ -1082,6 +1212,7 @@ function buildGenerateBody(options) {
|
|
|
1082
1212
|
const {
|
|
1083
1213
|
prompt,
|
|
1084
1214
|
model,
|
|
1215
|
+
creative,
|
|
1085
1216
|
aspect = "1:1",
|
|
1086
1217
|
resolution = "2K",
|
|
1087
1218
|
numImages = 1,
|
|
@@ -1116,7 +1247,8 @@ function buildGenerateBody(options) {
|
|
|
1116
1247
|
throw new Error(`Unknown model: ${model}`);
|
|
1117
1248
|
}
|
|
1118
1249
|
let endpoint = config.endpoint;
|
|
1119
|
-
const
|
|
1250
|
+
const enrichedPrompt = creative ? enrichPrompt({ prompt, creative }).prompt : prompt;
|
|
1251
|
+
const body = { prompt: enrichedPrompt };
|
|
1120
1252
|
const hasEditImages = Boolean(editImageUrls?.length);
|
|
1121
1253
|
const editImages = editImageUrls ?? [];
|
|
1122
1254
|
const explicitAspect = options.aspect !== void 0;
|
|
@@ -2062,6 +2194,23 @@ var FAL_TOOLS = {
|
|
|
2062
2194
|
output_format: "png"
|
|
2063
2195
|
}
|
|
2064
2196
|
},
|
|
2197
|
+
"sam3-1-image": {
|
|
2198
|
+
name: "SAM 3.1 Image",
|
|
2199
|
+
endpoint: "fal-ai/sam-3-1/image",
|
|
2200
|
+
task: "promptable image segmentation",
|
|
2201
|
+
category: "segmentation",
|
|
2202
|
+
description: "Segment image objects with text, point, or box prompts. SAM 3.1 adds Object Multiplex for faster multi-object tracking.",
|
|
2203
|
+
inputKind: "image",
|
|
2204
|
+
inputField: "image_url",
|
|
2205
|
+
outputKeys: ["image", "masks", "metadata", "scores", "boxes"],
|
|
2206
|
+
pricing: "$0.005/request",
|
|
2207
|
+
sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/image",
|
|
2208
|
+
defaultOptions: {
|
|
2209
|
+
apply_mask: true,
|
|
2210
|
+
max_masks: 3,
|
|
2211
|
+
output_format: "png"
|
|
2212
|
+
}
|
|
2213
|
+
},
|
|
2065
2214
|
"sam3-image-rle": {
|
|
2066
2215
|
name: "SAM 3 Image RLE",
|
|
2067
2216
|
endpoint: "fal-ai/sam-3/image-rle",
|
|
@@ -2208,6 +2357,7 @@ function buildFalToolRequest(options) {
|
|
|
2208
2357
|
// src/server.ts
|
|
2209
2358
|
var FAL_BASE_URL = "https://fal.run";
|
|
2210
2359
|
var FAL_QUEUE_URL = "https://queue.fal.run";
|
|
2360
|
+
var FAL_API_URL = "https://api.fal.ai";
|
|
2211
2361
|
var FAL_REST_URL = "https://rest.alpha.fal.ai";
|
|
2212
2362
|
function endpointFromQueueUrl(url, fallback) {
|
|
2213
2363
|
if (!url) return fallback;
|
|
@@ -2247,7 +2397,8 @@ var MotifServer = class {
|
|
|
2247
2397
|
const { endpoint, body } = buildGenerateBody(options);
|
|
2248
2398
|
const response = await this.request(`${FAL_BASE_URL}/${endpoint}`, {
|
|
2249
2399
|
method: "POST",
|
|
2250
|
-
body: JSON.stringify(body)
|
|
2400
|
+
body: JSON.stringify(body),
|
|
2401
|
+
headers: this.ephemeralHeaders(options)
|
|
2251
2402
|
});
|
|
2252
2403
|
if (response.isErr()) {
|
|
2253
2404
|
return err(response.error);
|
|
@@ -2288,7 +2439,8 @@ var MotifServer = class {
|
|
|
2288
2439
|
const { endpoint, body } = buildGenerateBody(options);
|
|
2289
2440
|
const response = await this.request(`${FAL_QUEUE_URL}/${endpoint}`, {
|
|
2290
2441
|
method: "POST",
|
|
2291
|
-
body: JSON.stringify(body)
|
|
2442
|
+
body: JSON.stringify(body),
|
|
2443
|
+
headers: this.ephemeralHeaders(options)
|
|
2292
2444
|
});
|
|
2293
2445
|
if (response.isErr()) {
|
|
2294
2446
|
return err(response.error);
|
|
@@ -2339,7 +2491,7 @@ var MotifServer = class {
|
|
|
2339
2491
|
return err(response.error);
|
|
2340
2492
|
}
|
|
2341
2493
|
const data = await response.value.json();
|
|
2342
|
-
return this.normalizeResponse(data);
|
|
2494
|
+
return this.normalizeResponse(data, requestId);
|
|
2343
2495
|
}
|
|
2344
2496
|
/** ─── Processing ──────────────────────────────────────────── */
|
|
2345
2497
|
/** Upscale an image using clarity or crystal upscaler. */
|
|
@@ -2554,6 +2706,23 @@ var MotifServer = class {
|
|
|
2554
2706
|
}
|
|
2555
2707
|
return ok(await response.value.json());
|
|
2556
2708
|
}
|
|
2709
|
+
/**
|
|
2710
|
+
* Delete fal's stored IO payloads for a completed request.
|
|
2711
|
+
*
|
|
2712
|
+
* This removes request input/output payload files exposed by fal's payloads
|
|
2713
|
+
* API. It does not remove billing/account metadata or input files separately
|
|
2714
|
+
* uploaded to fal storage before a request.
|
|
2715
|
+
*/
|
|
2716
|
+
async deletePayloads(requestId) {
|
|
2717
|
+
const response = await this.request(
|
|
2718
|
+
`${FAL_API_URL}/v1/models/requests/${encodeURIComponent(requestId)}/payloads`,
|
|
2719
|
+
{ method: "DELETE" }
|
|
2720
|
+
);
|
|
2721
|
+
if (response.isErr()) {
|
|
2722
|
+
return err(response.error);
|
|
2723
|
+
}
|
|
2724
|
+
return ok(void 0);
|
|
2725
|
+
}
|
|
2557
2726
|
/** Estimate cost for a generation (no API call). */
|
|
2558
2727
|
estimateCost(model, resolution, numImages) {
|
|
2559
2728
|
return estimateCost(model, resolution, numImages);
|
|
@@ -2628,12 +2797,16 @@ var MotifServer = class {
|
|
|
2628
2797
|
}
|
|
2629
2798
|
return err(lastError ?? new MotifError("Request failed after retries", 0));
|
|
2630
2799
|
}
|
|
2800
|
+
ephemeralHeaders(options) {
|
|
2801
|
+
return options.ephemeral ? { "X-Fal-Store-IO": "0" } : {};
|
|
2802
|
+
}
|
|
2631
2803
|
/**
|
|
2632
2804
|
* Normalize fal.ai responses.
|
|
2633
2805
|
* Some APIs return `{ image: {...} }` instead of `{ images: [...] }`.
|
|
2634
2806
|
*/
|
|
2635
|
-
normalizeResponse(data) {
|
|
2807
|
+
normalizeResponse(data, fallbackRequestId) {
|
|
2636
2808
|
const obj = data;
|
|
2809
|
+
const requestId = obj.request_id ?? obj.requestId ?? fallbackRequestId;
|
|
2637
2810
|
if ("detail" in obj) {
|
|
2638
2811
|
return err(
|
|
2639
2812
|
new MotifError(obj.detail, 0, "FAL_ERROR")
|
|
@@ -2643,10 +2816,14 @@ var MotifServer = class {
|
|
|
2643
2816
|
return ok({
|
|
2644
2817
|
images: [obj.image],
|
|
2645
2818
|
seed: obj.seed,
|
|
2646
|
-
prompt: obj.prompt
|
|
2819
|
+
prompt: obj.prompt,
|
|
2820
|
+
requestId
|
|
2647
2821
|
});
|
|
2648
2822
|
}
|
|
2649
|
-
return ok(
|
|
2823
|
+
return ok({
|
|
2824
|
+
...obj,
|
|
2825
|
+
requestId
|
|
2826
|
+
});
|
|
2650
2827
|
}
|
|
2651
2828
|
};
|
|
2652
2829
|
var MotifError = class extends Error {
|
|
@@ -2661,6 +2838,9 @@ var MotifError = class extends Error {
|
|
|
2661
2838
|
};
|
|
2662
2839
|
export {
|
|
2663
2840
|
ASPECT_RATIOS,
|
|
2841
|
+
CREATIVE_FIELDS,
|
|
2842
|
+
CREATIVE_TAXONOMY,
|
|
2843
|
+
CreativeOptionError,
|
|
2664
2844
|
FAL_TOOLS,
|
|
2665
2845
|
FAL_TOOLS_CHECKED_AT,
|
|
2666
2846
|
FAL_TOOL_IDS,
|
|
@@ -2682,6 +2862,7 @@ export {
|
|
|
2682
2862
|
aspectToGptSize,
|
|
2683
2863
|
buildFalToolRequest,
|
|
2684
2864
|
buildGenerateBody,
|
|
2865
|
+
enrichPrompt,
|
|
2685
2866
|
err2 as err,
|
|
2686
2867
|
estimateCost,
|
|
2687
2868
|
estimateVideoCost,
|
|
@@ -2689,5 +2870,6 @@ export {
|
|
|
2689
2870
|
isFalToolId,
|
|
2690
2871
|
motifEnvSchema,
|
|
2691
2872
|
ok2 as ok,
|
|
2692
|
-
parseMotifEnv
|
|
2873
|
+
parseMotifEnv,
|
|
2874
|
+
sanitizePrompt
|
|
2693
2875
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@howells/motif-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|
|
@@ -37,6 +37,17 @@
|
|
|
37
37
|
"publishConfig": {
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
42
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
43
|
+
"lint": "howells-lint",
|
|
44
|
+
"format": "howells-format",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"test:watch": "vitest",
|
|
48
|
+
"test:coverage": "vitest run --coverage",
|
|
49
|
+
"prepack": "pnpm build"
|
|
50
|
+
},
|
|
40
51
|
"dependencies": {
|
|
41
52
|
"@howells/envy": "^0.3.7",
|
|
42
53
|
"neverthrow": "^8.2.0",
|
|
@@ -50,15 +61,5 @@
|
|
|
50
61
|
"tsup": "^8.0.0",
|
|
51
62
|
"typescript": "^6.0.3",
|
|
52
63
|
"vitest": "^4.1.5"
|
|
53
|
-
},
|
|
54
|
-
"scripts": {
|
|
55
|
-
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
56
|
-
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
57
|
-
"lint": "howells-lint",
|
|
58
|
-
"format": "howells-format",
|
|
59
|
-
"typecheck": "tsc --noEmit",
|
|
60
|
-
"test": "vitest run",
|
|
61
|
-
"test:watch": "vitest",
|
|
62
|
-
"test:coverage": "vitest run --coverage"
|
|
63
64
|
}
|
|
64
|
-
}
|
|
65
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Daniel Howells
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|