@howells/motif-sdk 5.2.0 → 5.4.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/dist/index.d.cts CHANGED
@@ -246,7 +246,8 @@ export declare function enrichPrompt(options: EnrichPromptOptions): CreativeProm
246
246
  /** ─── Model Types ─────────────────────────────────────────────── */
247
247
  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";
248
248
  type Resolution = "0.5K" | "1K" | "2K" | "4K";
249
- type ImageOutputFormat = "jpeg" | "png" | "webp";
249
+ /** `gif` is an animation and `svg` a vector image, on the Models that make them. */
250
+ type ImageOutputFormat = "jpeg" | "png" | "webp" | "gif" | "svg";
250
251
  interface CustomImageSize {
251
252
  height: number;
252
253
  width: number;
@@ -325,25 +326,31 @@ export declare const ACCOUNT_LOCKED = "ACCOUNT_LOCKED";
325
326
  */
326
327
  export declare function isFalAccountLocked(status: number, body: string): boolean;
327
328
  //#endregion
328
- //#region src/tasks.d.ts
329
+ //#region src/image-benchmarks.d.ts
329
330
  /**
330
- * The Task registry: every job Motif does, with the Models that can do it
331
- * ranked best-first and tagged with the Tier each belongs to.
332
- *
333
- * Rankings are data so a default change is visible in a pull request. A Task
334
- * ranked from the bench's human comparisons says `rankedFrom: "bench"`; every
335
- * other Task is hand-ranked from published quality numbers and fal price, and
336
- * says so. Today no Task is bench-ranked: the bench holds two human ratings,
337
- * both on one model, which is not an order.
338
- *
339
- * Model ids are the short aliases `-m` and `TaskInput.model` accept. `resolveTask` in `./resolve` is the only place a Model is
340
- * chosen from these lists.
341
- *
342
- * A Task can have modes: a variant of the job a caller names explicitly, such
343
- * as erasing text rather than an object. A ranked entry with a `mode` is only
344
- * chosen when the request names that mode; an entry without one only when it
345
- * names none. A Model can appear once per mode.
331
+ * Arena image scores captured for MOT-72 on October 29, 2026. Flare and
332
+ * Sunburst route to GPT Image 2.6 but keep 2.5's scores until 2.6 is
333
+ * benchmarked; `evaluatedModel` names what was measured.
346
334
  */
335
+ type ImageBenchmarkTask = "text-to-image" | "image-edit-single-image";
336
+ interface ArenaScore {
337
+ readonly elo: number;
338
+ readonly rank: number;
339
+ /** The reported plus-or-minus interval, in Elo points. */
340
+ readonly uncertainty: number;
341
+ /** Exact leaderboard label, including a setting where one was reported. */
342
+ readonly evaluatedModel: string;
343
+ readonly preliminary?: true;
344
+ readonly votes?: number;
345
+ }
346
+ /** Scores are comparable only on the same board and snapshot. */
347
+ interface ImageModelBenchmark extends ArenaScore {
348
+ readonly source: string;
349
+ readonly snapshotDate: string;
350
+ readonly task: ImageBenchmarkTask;
351
+ }
352
+ //#endregion
353
+ //#region src/tasks.d.ts
347
354
  export declare const TIERS: readonly ["fast", "balanced", "quality"];
348
355
  /** How far a Task trades cost and speed for quality. */
349
356
  type Tier = (typeof TIERS)[number];
@@ -351,12 +358,14 @@ export declare const DEFAULT_TIER: Tier;
351
358
  /** Where a Task's order came from. */
352
359
  type RankedFrom = "bench" | "hand";
353
360
  /** Something a request needs that not every Model can honour. */
354
- type Capability = "aspect" | "count" | "image" | "mask" | "negativePrompt" | "outputFormat" | "references" | "resolution" | "rig" | "seed" | "transparency" | "video";
361
+ type Capability = "aspect" | "count" | "image" | "markup" | "mask" | "negativePrompt" | "outputFormat" | "references" | "resolution" | "rig" | "seed" | "transparency" | "video";
355
362
  interface RankedModel {
356
363
  /** A Model id, as accepted by `-m` or `TaskInput.model`. */
357
364
  readonly model: string;
358
365
  /** The Tier this Model is offered at. */
359
366
  readonly tier: Tier;
367
+ /** Published evidence for this Task; null means this route is unscored. */
368
+ readonly benchmark?: ImageModelBenchmark | null;
360
369
  /** The mode this entry serves. Absent for the Task's plain job. */
361
370
  readonly mode?: string;
362
371
  /** Capabilities this Model has for this Task beyond its registry profile. */
@@ -524,10 +533,10 @@ export declare const TASKS: {
524
533
  readonly summary: "Remove an object, person, text or clutter from an image and fill the gap.";
525
534
  };
526
535
  readonly generate: {
527
- readonly basis: "Curated from Artificial Analysis v2, captured 2026-10-07, and Arena 2026-10-06. Sunburst and Flare lead quality at the benchmarked max setting. Nano Banana 2.1 leads balanced on preliminary Arena evidence and lower representative cost; Grok Image 2 and MAI 2.5 are established alternatives. Fast keeps Nano Banana 2 Lite, budget Grok and Seedream 5 Lite, and FLUX.2 Turbo. Design, transparency and tuned Look routes are capability exceptions. Generation with References uses the separate editing ranking. Older routes are explicit/pin/Look-only.";
536
+ readonly basis: "Order follows Arena text-to-image (https://arena.ai/leaderboard/image/text-to-image), read 2026-10-29 (MOT-72). Sunburst and Flare lead quality at the benchmarked max setting. Balanced starts with Nano Banana 2.1 on preliminary Arena evidence, then Grok Image 2 and Muse Image. Models not yet on the board follow the ranked ones in their Tier, in their previous order. The Artificial Analysis v2 snapshot of 2026-10-07 stays as recorded benchmark data. Fast keeps Nano Banana 2 Lite, budget Grok and Seedream 5 Lite, and FLUX.2 Turbo. Design, transparency and tuned Look routes are capability exceptions. Generation with References uses the separate editing ranking. Older routes are explicit/pin/Look-only.";
528
537
  readonly models: readonly RankedModel[];
529
538
  readonly notFor: "Variations of an image you already have (vary), or a consistent set of images (series run).";
530
- readonly rankedAt: "2026-10-07";
539
+ readonly rankedAt: "2026-10-29";
531
540
  readonly rankedFrom: "hand";
532
541
  readonly summary: "Make an image from a prompt, or change an image you pass as a reference.";
533
542
  };
@@ -937,10 +946,10 @@ export declare const TASKS: {
937
946
  readonly summary: "Trace a raster image to a clean SVG.";
938
947
  };
939
948
  readonly vary: {
940
- readonly basis: "Curated from Artificial Analysis v2 editing, captured 2026-10-07, and preliminary Arena 2026-10-06 evidence for Nano Banana 2.1. Editing order differs from generation: MAI 2.5 outranks Grok Image 2, and Seedream 5 Pro outranks GPT Image 1.5. Sunburst and Flare use max quality. vary reuses a previously chosen Model when explicitly supplied, including retained legacy routes.";
949
+ readonly basis: "Order follows Arena image edit, single image (https://arena.ai/leaderboard/image/image-edit/single-image), read 2026-10-29 (MOT-72). Editing order differs from generation: Grok Image 2 leads balanced, ahead of Nano Banana 2.1, and Seedream 5 Pro outranks GPT Image 1.5. Arena scores Krea 3 only at its Ultra setting, which costs $0.27 to $0.35 an edit and ignores the requested size, so krea3, which runs fal's default setting, is ranked as unscored. Models not yet on the board follow the ranked ones in their Tier, in their previous order. The Artificial Analysis v2 snapshot of 2026-10-07 stays as recorded benchmark data. Sunburst and Flare use max quality. vary reuses a previously chosen Model when explicitly supplied, including retained legacy routes.";
941
950
  readonly models: readonly RankedModel[];
942
951
  readonly notFor: "A specific change to an image described in words (generate with a reference), or a set of different scenes in one style (series run).";
943
- readonly rankedAt: "2026-10-07";
952
+ readonly rankedAt: "2026-10-29";
944
953
  readonly rankedFrom: "hand";
945
954
  readonly summary: "Make variations of an image you already have.";
946
955
  };
@@ -964,6 +973,8 @@ interface TaskRequest {
964
973
  readonly references?: number;
965
974
  /** A mask image is passed in. */
966
975
  readonly mask?: boolean;
976
+ /** A markup image is passed in. */
977
+ readonly markup?: boolean;
967
978
  /** An aspect ratio is asked for, e.g. `"3:2"`. */
968
979
  readonly aspect?: string;
969
980
  /** A resolution tier is asked for, e.g. `"1K"`. */
@@ -972,7 +983,8 @@ interface TaskRequest {
972
983
  readonly count?: number;
973
984
  readonly seed?: boolean;
974
985
  readonly negativePrompt?: boolean;
975
- readonly outputFormat?: boolean;
986
+ /** An output format is asked for, e.g. `"gif"`. */
987
+ readonly outputFormat?: string;
976
988
  /** A rigged mesh wanted. */
977
989
  readonly rig?: boolean;
978
990
  /** The kind of Source passed in. Absent for a text-only request. */
@@ -1027,6 +1039,12 @@ interface ModelProfile {
1027
1039
  readonly aspects?: readonly string[];
1028
1040
  /** Resolutions the Model accepts. Absent means any. */
1029
1041
  readonly resolutions?: readonly string[];
1042
+ /** Output formats the Model returns, for a text request and an edit.
1043
+ * Absent means any. */
1044
+ readonly outputFormats?: {
1045
+ readonly edit: readonly string[];
1046
+ readonly text: readonly string[];
1047
+ };
1030
1048
  }
1031
1049
  /** The profile for a Model id, or undefined when no registry knows it. */
1032
1050
  export declare function modelProfile(model: string): ModelProfile | undefined;
@@ -1170,6 +1188,12 @@ interface TaskInput {
1170
1188
  prompt?: string;
1171
1189
  /** Mask image: https URL or data URL. */
1172
1190
  mask?: string;
1191
+ /**
1192
+ * Markup image: https URL or data URL. A transparent PNG of annotations
1193
+ * (circles, arrows, notes) the size of the image being edited, guiding its
1194
+ * edit. generate with references and vary take it; it can't join a mask.
1195
+ */
1196
+ markup?: string;
1173
1197
  /** Reference images: https URLs or data URLs. restyle and try-on take exactly one. */
1174
1198
  references?: readonly string[];
1175
1199
  aspect?: AspectRatio;
@@ -1198,10 +1222,15 @@ interface TaskInput {
1198
1222
  ephemeral?: boolean;
1199
1223
  /**
1200
1224
  * Called on each poll of fal's queue with its status ("queued",
1201
- * "processing", "completed", "failed") and, while queued, the place in line. Passing
1202
- * it runs a fal generation through the queue, since only the queue reports.
1225
+ * "processing", "completed", "failed") and, while queued, the place in line. When fal has
1226
+ * written new log lines since the last poll, they come as the third argument
1227
+ * (new lines only). Passing it runs a fal generation through the queue, since
1228
+ * only the queue reports.
1203
1229
  */
1204
- onProgress?: (status: JobStatus["status"], queuePosition?: number) => void;
1230
+ onProgress?: (status: JobStatus["status"], queuePosition?: number, logs?: readonly {
1231
+ message: string;
1232
+ timestamp: string;
1233
+ }[]) => void;
1205
1234
  /**
1206
1235
  * Called once, as soon as fal's queue accepts the run, with its request id
1207
1236
  * and before any wait. Keep it to `resume` the same job after a restart
package/dist/index.d.ts CHANGED
@@ -246,7 +246,8 @@ export declare function enrichPrompt(options: EnrichPromptOptions): CreativeProm
246
246
  /** ─── Model Types ─────────────────────────────────────────────── */
247
247
  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";
248
248
  type Resolution = "0.5K" | "1K" | "2K" | "4K";
249
- type ImageOutputFormat = "jpeg" | "png" | "webp";
249
+ /** `gif` is an animation and `svg` a vector image, on the Models that make them. */
250
+ type ImageOutputFormat = "jpeg" | "png" | "webp" | "gif" | "svg";
250
251
  interface CustomImageSize {
251
252
  height: number;
252
253
  width: number;
@@ -325,25 +326,31 @@ export declare const ACCOUNT_LOCKED = "ACCOUNT_LOCKED";
325
326
  */
326
327
  export declare function isFalAccountLocked(status: number, body: string): boolean;
327
328
  //#endregion
328
- //#region src/tasks.d.ts
329
+ //#region src/image-benchmarks.d.ts
329
330
  /**
330
- * The Task registry: every job Motif does, with the Models that can do it
331
- * ranked best-first and tagged with the Tier each belongs to.
332
- *
333
- * Rankings are data so a default change is visible in a pull request. A Task
334
- * ranked from the bench's human comparisons says `rankedFrom: "bench"`; every
335
- * other Task is hand-ranked from published quality numbers and fal price, and
336
- * says so. Today no Task is bench-ranked: the bench holds two human ratings,
337
- * both on one model, which is not an order.
338
- *
339
- * Model ids are the short aliases `-m` and `TaskInput.model` accept. `resolveTask` in `./resolve` is the only place a Model is
340
- * chosen from these lists.
341
- *
342
- * A Task can have modes: a variant of the job a caller names explicitly, such
343
- * as erasing text rather than an object. A ranked entry with a `mode` is only
344
- * chosen when the request names that mode; an entry without one only when it
345
- * names none. A Model can appear once per mode.
331
+ * Arena image scores captured for MOT-72 on October 29, 2026. Flare and
332
+ * Sunburst route to GPT Image 2.6 but keep 2.5's scores until 2.6 is
333
+ * benchmarked; `evaluatedModel` names what was measured.
346
334
  */
335
+ type ImageBenchmarkTask = "text-to-image" | "image-edit-single-image";
336
+ interface ArenaScore {
337
+ readonly elo: number;
338
+ readonly rank: number;
339
+ /** The reported plus-or-minus interval, in Elo points. */
340
+ readonly uncertainty: number;
341
+ /** Exact leaderboard label, including a setting where one was reported. */
342
+ readonly evaluatedModel: string;
343
+ readonly preliminary?: true;
344
+ readonly votes?: number;
345
+ }
346
+ /** Scores are comparable only on the same board and snapshot. */
347
+ interface ImageModelBenchmark extends ArenaScore {
348
+ readonly source: string;
349
+ readonly snapshotDate: string;
350
+ readonly task: ImageBenchmarkTask;
351
+ }
352
+ //#endregion
353
+ //#region src/tasks.d.ts
347
354
  export declare const TIERS: readonly ["fast", "balanced", "quality"];
348
355
  /** How far a Task trades cost and speed for quality. */
349
356
  type Tier = (typeof TIERS)[number];
@@ -351,12 +358,14 @@ export declare const DEFAULT_TIER: Tier;
351
358
  /** Where a Task's order came from. */
352
359
  type RankedFrom = "bench" | "hand";
353
360
  /** Something a request needs that not every Model can honour. */
354
- type Capability = "aspect" | "count" | "image" | "mask" | "negativePrompt" | "outputFormat" | "references" | "resolution" | "rig" | "seed" | "transparency" | "video";
361
+ type Capability = "aspect" | "count" | "image" | "markup" | "mask" | "negativePrompt" | "outputFormat" | "references" | "resolution" | "rig" | "seed" | "transparency" | "video";
355
362
  interface RankedModel {
356
363
  /** A Model id, as accepted by `-m` or `TaskInput.model`. */
357
364
  readonly model: string;
358
365
  /** The Tier this Model is offered at. */
359
366
  readonly tier: Tier;
367
+ /** Published evidence for this Task; null means this route is unscored. */
368
+ readonly benchmark?: ImageModelBenchmark | null;
360
369
  /** The mode this entry serves. Absent for the Task's plain job. */
361
370
  readonly mode?: string;
362
371
  /** Capabilities this Model has for this Task beyond its registry profile. */
@@ -524,10 +533,10 @@ export declare const TASKS: {
524
533
  readonly summary: "Remove an object, person, text or clutter from an image and fill the gap.";
525
534
  };
526
535
  readonly generate: {
527
- readonly basis: "Curated from Artificial Analysis v2, captured 2026-10-07, and Arena 2026-10-06. Sunburst and Flare lead quality at the benchmarked max setting. Nano Banana 2.1 leads balanced on preliminary Arena evidence and lower representative cost; Grok Image 2 and MAI 2.5 are established alternatives. Fast keeps Nano Banana 2 Lite, budget Grok and Seedream 5 Lite, and FLUX.2 Turbo. Design, transparency and tuned Look routes are capability exceptions. Generation with References uses the separate editing ranking. Older routes are explicit/pin/Look-only.";
536
+ readonly basis: "Order follows Arena text-to-image (https://arena.ai/leaderboard/image/text-to-image), read 2026-10-29 (MOT-72). Sunburst and Flare lead quality at the benchmarked max setting. Balanced starts with Nano Banana 2.1 on preliminary Arena evidence, then Grok Image 2 and Muse Image. Models not yet on the board follow the ranked ones in their Tier, in their previous order. The Artificial Analysis v2 snapshot of 2026-10-07 stays as recorded benchmark data. Fast keeps Nano Banana 2 Lite, budget Grok and Seedream 5 Lite, and FLUX.2 Turbo. Design, transparency and tuned Look routes are capability exceptions. Generation with References uses the separate editing ranking. Older routes are explicit/pin/Look-only.";
528
537
  readonly models: readonly RankedModel[];
529
538
  readonly notFor: "Variations of an image you already have (vary), or a consistent set of images (series run).";
530
- readonly rankedAt: "2026-10-07";
539
+ readonly rankedAt: "2026-10-29";
531
540
  readonly rankedFrom: "hand";
532
541
  readonly summary: "Make an image from a prompt, or change an image you pass as a reference.";
533
542
  };
@@ -937,10 +946,10 @@ export declare const TASKS: {
937
946
  readonly summary: "Trace a raster image to a clean SVG.";
938
947
  };
939
948
  readonly vary: {
940
- readonly basis: "Curated from Artificial Analysis v2 editing, captured 2026-10-07, and preliminary Arena 2026-10-06 evidence for Nano Banana 2.1. Editing order differs from generation: MAI 2.5 outranks Grok Image 2, and Seedream 5 Pro outranks GPT Image 1.5. Sunburst and Flare use max quality. vary reuses a previously chosen Model when explicitly supplied, including retained legacy routes.";
949
+ readonly basis: "Order follows Arena image edit, single image (https://arena.ai/leaderboard/image/image-edit/single-image), read 2026-10-29 (MOT-72). Editing order differs from generation: Grok Image 2 leads balanced, ahead of Nano Banana 2.1, and Seedream 5 Pro outranks GPT Image 1.5. Arena scores Krea 3 only at its Ultra setting, which costs $0.27 to $0.35 an edit and ignores the requested size, so krea3, which runs fal's default setting, is ranked as unscored. Models not yet on the board follow the ranked ones in their Tier, in their previous order. The Artificial Analysis v2 snapshot of 2026-10-07 stays as recorded benchmark data. Sunburst and Flare use max quality. vary reuses a previously chosen Model when explicitly supplied, including retained legacy routes.";
941
950
  readonly models: readonly RankedModel[];
942
951
  readonly notFor: "A specific change to an image described in words (generate with a reference), or a set of different scenes in one style (series run).";
943
- readonly rankedAt: "2026-10-07";
952
+ readonly rankedAt: "2026-10-29";
944
953
  readonly rankedFrom: "hand";
945
954
  readonly summary: "Make variations of an image you already have.";
946
955
  };
@@ -964,6 +973,8 @@ interface TaskRequest {
964
973
  readonly references?: number;
965
974
  /** A mask image is passed in. */
966
975
  readonly mask?: boolean;
976
+ /** A markup image is passed in. */
977
+ readonly markup?: boolean;
967
978
  /** An aspect ratio is asked for, e.g. `"3:2"`. */
968
979
  readonly aspect?: string;
969
980
  /** A resolution tier is asked for, e.g. `"1K"`. */
@@ -972,7 +983,8 @@ interface TaskRequest {
972
983
  readonly count?: number;
973
984
  readonly seed?: boolean;
974
985
  readonly negativePrompt?: boolean;
975
- readonly outputFormat?: boolean;
986
+ /** An output format is asked for, e.g. `"gif"`. */
987
+ readonly outputFormat?: string;
976
988
  /** A rigged mesh wanted. */
977
989
  readonly rig?: boolean;
978
990
  /** The kind of Source passed in. Absent for a text-only request. */
@@ -1027,6 +1039,12 @@ interface ModelProfile {
1027
1039
  readonly aspects?: readonly string[];
1028
1040
  /** Resolutions the Model accepts. Absent means any. */
1029
1041
  readonly resolutions?: readonly string[];
1042
+ /** Output formats the Model returns, for a text request and an edit.
1043
+ * Absent means any. */
1044
+ readonly outputFormats?: {
1045
+ readonly edit: readonly string[];
1046
+ readonly text: readonly string[];
1047
+ };
1030
1048
  }
1031
1049
  /** The profile for a Model id, or undefined when no registry knows it. */
1032
1050
  export declare function modelProfile(model: string): ModelProfile | undefined;
@@ -1170,6 +1188,12 @@ interface TaskInput {
1170
1188
  prompt?: string;
1171
1189
  /** Mask image: https URL or data URL. */
1172
1190
  mask?: string;
1191
+ /**
1192
+ * Markup image: https URL or data URL. A transparent PNG of annotations
1193
+ * (circles, arrows, notes) the size of the image being edited, guiding its
1194
+ * edit. generate with references and vary take it; it can't join a mask.
1195
+ */
1196
+ markup?: string;
1173
1197
  /** Reference images: https URLs or data URLs. restyle and try-on take exactly one. */
1174
1198
  references?: readonly string[];
1175
1199
  aspect?: AspectRatio;
@@ -1198,10 +1222,15 @@ interface TaskInput {
1198
1222
  ephemeral?: boolean;
1199
1223
  /**
1200
1224
  * Called on each poll of fal's queue with its status ("queued",
1201
- * "processing", "completed", "failed") and, while queued, the place in line. Passing
1202
- * it runs a fal generation through the queue, since only the queue reports.
1225
+ * "processing", "completed", "failed") and, while queued, the place in line. When fal has
1226
+ * written new log lines since the last poll, they come as the third argument
1227
+ * (new lines only). Passing it runs a fal generation through the queue, since
1228
+ * only the queue reports.
1203
1229
  */
1204
- onProgress?: (status: JobStatus["status"], queuePosition?: number) => void;
1230
+ onProgress?: (status: JobStatus["status"], queuePosition?: number, logs?: readonly {
1231
+ message: string;
1232
+ timestamp: string;
1233
+ }[]) => void;
1205
1234
  /**
1206
1235
  * Called once, as soon as fal's queue accepts the run, with its request id
1207
1236
  * and before any wait. Keep it to `resume` the same job after a restart