@howells/motif-sdk 7.0.0 → 7.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/dist/index.d.ts CHANGED
@@ -1,6 +1,18 @@
1
+ /**
2
+ * Motif SDK: task-first image work over fal.ai.
3
+ *
4
+ * Callers name a task and a tier; Motif picks the model. The per-model registry
5
+ * and the fal client stay internal.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { createMotif } from "@howells/motif-sdk";
10
+ *
11
+ * const motif = createMotif();
12
+ * ```
13
+ */
1
14
  import { Result, Result as Result$1, ResultAsync, err, ok } from "neverthrow";
2
15
  import { z } from "zod";
3
- //#region src/creative.d.ts
4
16
  /**
5
17
  * Creative direction fields applied to prompts in Motif's canonical order.
6
18
  *
@@ -241,8 +253,6 @@ export declare function validateCreativeDirection(creative: CreativeDirection |
241
253
  * are joined with `". "` and closed with a period. With no clauses the sanitized prompt is returned unchanged.
242
254
  */
243
255
  export declare function enrichPrompt(options: EnrichPromptOptions): CreativePromptResult;
244
- //#endregion
245
- //#region src/types.d.ts
246
256
  /** ─── Model Types ─────────────────────────────────────────────── */
247
257
  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
258
  type Resolution = "0.5K" | "1K" | "2K" | "4K";
@@ -275,8 +285,6 @@ interface JobStatus {
275
285
  /** ─── Configuration ──────────────────────────────────────────── */
276
286
  /** The network seam: a `fetch`-shaped function. */
277
287
  type FalFetch = (url: string, init: RequestInit) => Promise<Response>;
278
- //#endregion
279
- //#region src/aspects.d.ts
280
288
  /** Ordered by popularity: square first, then common ratios */
281
289
  export declare const ASPECT_RATIOS: AspectRatio[];
282
290
  export declare const RESOLUTIONS: Resolution[];
@@ -296,8 +304,6 @@ export declare const FORMAT_PRESETS: Record<string, {
296
304
  aspect: AspectRatio;
297
305
  resolution?: Resolution;
298
306
  }>;
299
- //#endregion
300
- //#region src/errors.d.ts
301
307
  /**
302
308
  * `MotifError` and its coercion helper.
303
309
  *
@@ -325,8 +331,6 @@ export declare const ACCOUNT_LOCKED = "ACCOUNT_LOCKED";
325
331
  * account has run out of credit. Retrying cannot help, and the key is valid.
326
332
  */
327
333
  export declare function isFalAccountLocked(status: number, body: string): boolean;
328
- //#endregion
329
- //#region src/image-benchmarks.d.ts
330
334
  /**
331
335
  * Arena image scores captured for MOT-72 on October 29, 2026. Flare and
332
336
  * Sunburst route to GPT Image 2.6 but keep 2.5's scores until 2.6 is
@@ -340,6 +344,8 @@ interface ArenaScore {
340
344
  readonly uncertainty: number;
341
345
  /** Exact leaderboard label, including a setting where one was reported. */
342
346
  readonly evaluatedModel: string;
347
+ /** Set when the score was measured on a different version than Motif runs. */
348
+ readonly note?: string;
343
349
  readonly preliminary?: true;
344
350
  readonly votes?: number;
345
351
  }
@@ -349,8 +355,6 @@ interface ImageModelBenchmark extends ArenaScore {
349
355
  readonly snapshotDate: string;
350
356
  readonly task: ImageBenchmarkTask;
351
357
  }
352
- //#endregion
353
- //#region src/tasks.d.ts
354
358
  export declare const TIERS: readonly ["fast", "balanced", "quality"];
355
359
  /** How far a Task trades cost and speed for quality. */
356
360
  type Tier = (typeof TIERS)[number];
@@ -958,8 +962,6 @@ export declare const TASKS: {
958
962
  type TaskId = keyof typeof TASKS;
959
963
  export declare const TASK_IDS: readonly TaskId[];
960
964
  export declare function isTaskId(value: string): value is TaskId;
961
- //#endregion
962
- //#region src/resolve.d.ts
963
965
  /** What the request needs. Only the fields that narrow the choice. */
964
966
  interface TaskRequest {
965
967
  /** Explicit Model id, e.g. from `--model`. Overrides everything. */
@@ -1029,6 +1031,8 @@ interface TaskUnresolved {
1029
1031
  }
1030
1032
  type TaskResolution = TaskResolved | TaskUnresolved;
1031
1033
  interface ModelProfile {
1034
+ /** The Model's display name, e.g. "GPT Image 2.6 Sunburst". */
1035
+ readonly name: string;
1032
1036
  readonly capabilities: ReadonlySet<Capability>;
1033
1037
  /** Key variables the Model needs for any request. */
1034
1038
  readonly keys: readonly string[];
@@ -1059,8 +1063,6 @@ export declare function resolveTask(task: TaskId, request: TaskRequest, environm
1059
1063
  * nothing there.
1060
1064
  */
1061
1065
  export declare function tierChangesChoice(task: TaskId, request: Omit<TaskRequest, "tier">, environment: TaskEnvironment): boolean;
1062
- //#endregion
1063
- //#region src/task-stream.d.ts
1064
1066
  interface TaskStreamOptions {
1065
1067
  signal?: AbortSignal;
1066
1068
  /** Overall deadline covering connection and consumption, in milliseconds. */
@@ -1091,8 +1093,6 @@ interface TaskStream {
1091
1093
  /** Stops local consumption; does not guarantee provider cancellation or refund. */
1092
1094
  abort: () => void;
1093
1095
  }
1094
- //#endregion
1095
- //#region src/tool-cost.d.ts
1096
1096
  /** Pixel dimensions of one file an endpoint returned. */
1097
1097
  interface OutputDimensions {
1098
1098
  height?: number;
@@ -1133,8 +1133,6 @@ export declare function sumCosts(costs: readonly (number | null)[]): {
1133
1133
  known: number;
1134
1134
  unknown: number;
1135
1135
  };
1136
- //#endregion
1137
- //#region src/task-client.d.ts
1138
1136
  interface MotifClientConfig {
1139
1137
  /** Falls back to FAL_KEY. */
1140
1138
  falKey?: string;
@@ -1309,11 +1307,7 @@ interface MotifClient {
1309
1307
  }
1310
1308
  /** Create the Task client. The key falls back to FAL_KEY. */
1311
1309
  export declare function createMotif(config?: MotifClientConfig): MotifClient;
1312
- //#endregion
1313
- //#region src/task-plan-shared.d.ts
1314
1310
  export declare const INVALID_OPTION = "INVALID_OPTION";
1315
- //#endregion
1316
- //#region src/prompt-warnings.d.ts
1317
1311
  /**
1318
1312
  * Advisory checks on a user-written prompt.
1319
1313
  *
@@ -1348,8 +1342,6 @@ interface PromptWarning {
1348
1342
  export declare function promptWarnings(prompt: string, context?: {
1349
1343
  editing?: boolean;
1350
1344
  }): PromptWarning[];
1351
- //#endregion
1352
- //#region src/env.d.ts
1353
1345
  export declare const motifEnvSchema: import("@howells/envy").EnvSchema<{
1354
1346
  readonly optional: {
1355
1347
  FAL_KEY: z.ZodString;
@@ -1358,5 +1350,4 @@ export declare const motifEnvSchema: import("@howells/envy").EnvSchema<{
1358
1350
  type MotifEnv = ReturnType<typeof motifEnvSchema.parse>;
1359
1351
  export declare function parseMotifEnv(input?: Record<string, unknown>): MotifEnv;
1360
1352
  export declare function getFalKeyFromEnv(input?: Record<string, unknown>): string | undefined;
1361
- //#endregion
1362
1353
  export { type AspectRatio, type Blocker, type Capability, type ChosenBy, type CreativeDirection, type CreativeField, type CreativeOption, type CreativeOptionErrorDetails, type CreativePromptResult, type CustomImageSize, type EnrichPromptOptions, type FalFetch, type ImageSizeBounds, type LookId, type LookOption, type ModelProfile, type MoodId, type MotifClient, type MotifClientConfig, type MotifEnv, type OutputDimensions, type PlanOptions, type PromptWarning, type PromptWarningRule, type RankedFrom, type RankedModel, type Resolution, type ResolvedCost, type Result, type ResultAsync, type TaskDefinition, type TaskEnvironment, type TaskFile, type TaskFunction, type TaskId, type TaskInput, type TaskMode, type TaskOutput, type TaskPlan, type TaskRequest, type TaskResolution, type TaskResolved, type TaskStream, type TaskStreamEvent, type TaskStreamOptions, type TaskUnresolved, type Tier, type Unblocker, err, ok };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { err, err as err$1, ok, ok as ok$1 } from "neverthrow";
2
2
  import { defineEnv } from "@howells/envy";
3
3
  import { z } from "zod";
4
- //#region src/aspects.ts
4
+ // src/aspects.ts
5
5
  /** Ordered by popularity: square first, then common ratios */
6
6
  const ASPECT_RATIOS = [
7
7
  "auto",
@@ -141,6 +141,9 @@ function scaleWithin(longEdge, longTerm, unit) {
141
141
  function roundUpTo(value, unit) {
142
142
  return unit * Math.ceil(value / unit);
143
143
  }
144
+ function roundDownTo(value, unit) {
145
+ return unit * Math.floor(value / unit);
146
+ }
144
147
  /**
145
148
  * Exact pixel dimensions for an aspect ratio at a resolution tier, or null for
146
149
  * `"auto"`.
@@ -170,8 +173,9 @@ function withinImageSizeBounds(size, bounds) {
170
173
  }
171
174
  /**
172
175
  * The exact size for an aspect on an endpoint with limits: the long edge as
173
- * close to `longEdge` as the ratio allows without passing it, then scaled up
174
- * only as far as a minimum edge or pixel count demands. Null for `"auto"`.
176
+ * close to `longEdge` as the ratio allows without passing it or the endpoint's
177
+ * maximum edge or pixel count, then scaled up only as far as a minimum edge or
178
+ * pixel count demands. Null for `"auto"`.
175
179
  * Undefined when no size in that ratio fits the limits: the caller refuses,
176
180
  * because rounding to another ratio is the defect this exists to remove.
177
181
  */
@@ -181,6 +185,8 @@ function boundedImageSize(aspect, longEdge, bounds) {
181
185
  const [width, height] = terms;
182
186
  const unit = bounds.multipleOf ?? EDGE_MULTIPLE;
183
187
  let scale = scaleWithin(longEdge, Math.max(width, height), unit);
188
+ if (bounds.maxEdge !== void 0) scale = Math.min(scale, roundDownTo(bounds.maxEdge / Math.max(width, height), unit));
189
+ if (bounds.maxPixels !== void 0) scale = Math.min(scale, roundDownTo(Math.sqrt(bounds.maxPixels / (width * height)), unit));
184
190
  if (bounds.minEdge !== void 0) scale = Math.max(scale, roundUpTo(bounds.minEdge / Math.min(width, height), unit));
185
191
  if (bounds.minPixels !== void 0) scale = Math.max(scale, roundUpTo(Math.sqrt(bounds.minPixels / (width * height)), unit));
186
192
  const size = {
@@ -236,8 +242,7 @@ const FORMAT_PRESETS = {
236
242
  label: "Wide"
237
243
  }
238
244
  };
239
- //#endregion
240
- //#region src/creative.ts
245
+ // src/creative.ts
241
246
  const CONTROL_CHAR_REGEX = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g;
242
247
  const TRAILING_PERIODS_REGEX = /\.+$/;
243
248
  /**
@@ -479,8 +484,7 @@ function enrichPrompt(options) {
479
484
  prompt: clauses.length ? joinSentences([basePrompt, ...clauses].filter((part) => part !== "")) : basePrompt
480
485
  };
481
486
  }
482
- //#endregion
483
- //#region src/errors.ts
487
+ // src/errors.ts
484
488
  /**
485
489
  * `MotifError` and its coercion helper.
486
490
  *
@@ -537,8 +541,7 @@ function toMotifError(error) {
537
541
  if (error instanceof MotifError) return error;
538
542
  return new MotifError(error instanceof Error ? error.message : String(error), 0, error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0);
539
543
  }
540
- //#endregion
541
- //#region src/task-plan-shared.ts
544
+ // src/task-plan-shared.ts
542
545
  /**
543
546
  * Helpers every request builder in the Task planner shares: refusing fields a
544
547
  * Model can't carry, and merging `params` without letting them replace what
@@ -618,8 +621,7 @@ function mergeParams(body, params, owned) {
618
621
  ...params
619
622
  });
620
623
  }
621
- //#endregion
622
- //#region src/prompt-warnings.ts
624
+ // src/prompt-warnings.ts
623
625
  const EDIT_VERB_REGEX = /^\s*(remove|erase|extend|outpaint)\b/i;
624
626
  /** The CLI verb that does each edit directly, keyed by the prompt's first word. */
625
627
  const EDIT_VERBS = {
@@ -685,8 +687,7 @@ function promptWarnings(prompt, context = {}) {
685
687
  });
686
688
  return warnings;
687
689
  }
688
- //#endregion
689
- //#region src/env.ts
690
+ // src/env.ts
690
691
  const motifEnvSchema = defineEnv({ optional: { FAL_KEY: z.string().min(1) } });
691
692
  function parseMotifEnv(input = process.env) {
692
693
  return motifEnvSchema.parse(input);
@@ -694,8 +695,7 @@ function parseMotifEnv(input = process.env) {
694
695
  function getFalKeyFromEnv(input = process.env) {
695
696
  return parseMotifEnv(input).FAL_KEY;
696
697
  }
697
- //#endregion
698
- //#region src/models.ts
698
+ // src/models.ts
699
699
  const AA_IMAGE_LEADERBOARD_SNAPSHOT = "2026-10-07";
700
700
  const AA_IMAGE_SOURCES = ["https://artificialanalysis.ai/image/leaderboard/text-to-image", "https://artificialanalysis.ai/image/leaderboard/editing"];
701
701
  const FAL_PRICING_CHECKED_AT = "2026-05-12";
@@ -765,7 +765,7 @@ const MODELS = {
765
765
  "xhigh",
766
766
  "max"
767
767
  ],
768
- supportsResolution: false,
768
+ supportsResolution: true,
769
769
  supportsSyncMode: true,
770
770
  type: "generation",
771
771
  useQueue: true
@@ -832,7 +832,7 @@ const MODELS = {
832
832
  "xhigh",
833
833
  "max"
834
834
  ],
835
- supportsResolution: false,
835
+ supportsResolution: true,
836
836
  supportsSyncMode: true,
837
837
  type: "generation",
838
838
  useQueue: true
@@ -1866,8 +1866,7 @@ const GENERATION_MODELS = [
1866
1866
  "recraft41"
1867
1867
  ];
1868
1868
  GENERATION_MODELS.filter((id) => MODELS[id]?.supportsEdit === true);
1869
- //#endregion
1870
- //#region src/cost.ts
1869
+ // src/cost.ts
1871
1870
  /**
1872
1871
  * Estimate cost based on model and settings. `editImages` is the number of
1873
1872
  * input images on an edit, for Models whose edits cost more than generation.
@@ -1897,8 +1896,7 @@ function estimateVideoCost(durationSeconds = 5, generateAudio = true, model = "k
1897
1896
  if (model === "kling-turbo") return .14 * durationSeconds;
1898
1897
  return (generateAudio ? .168 : .112) * durationSeconds;
1899
1898
  }
1900
- //#endregion
1901
- //#region src/fal-parse.ts
1899
+ // src/fal-parse.ts
1902
1900
  /**
1903
1901
  * Boundary parsing for fal.ai HTTP responses.
1904
1902
  *
@@ -2003,8 +2001,7 @@ function requestIdFromBody(text) {
2003
2001
  if (!isRecord$1(parsed)) return;
2004
2002
  return asString(parsed.request_id) ?? asString(parsed.requestId) ?? asString(parsed.trace_id);
2005
2003
  }
2006
- //#endregion
2007
- //#region src/capabilities.ts
2004
+ // src/capabilities.ts
2008
2005
  /**
2009
2006
  * Which generation options each model accepts, derived from the registry.
2010
2007
  *
@@ -2094,8 +2091,7 @@ var UnsupportedOptionError = class extends Error {
2094
2091
  this.supportedOptions = supported;
2095
2092
  }
2096
2093
  };
2097
- //#endregion
2098
- //#region src/generate.ts
2094
+ // src/generate.ts
2099
2095
  const GPT_IMAGE_SIZES = [
2100
2096
  "auto",
2101
2097
  "1024x1024",
@@ -2133,15 +2129,16 @@ function normalizeImageSize(imageSize, sizeMode) {
2133
2129
  throw new Error(`fal image_size must be one of ${FAL_IMAGE_SIZE_PRESETS.join(", ")} or WIDTHxHEIGHT`);
2134
2130
  }
2135
2131
  /**
2136
- * `image_size` for an aspect: fal's named preset where one holds the ratio
2137
- * exactly, else exact dimensions where the endpoint takes them, else the
2138
- * nearest preset. A Model without a resolution setting is sized to the preset
2132
+ * `image_size` for an aspect: exact dimensions at the requested resolution
2133
+ * where the endpoint takes them and has a resolution setting; otherwise fal's
2134
+ * named preset where one holds the ratio exactly, else exact dimensions where
2135
+ * the endpoint takes them, else the nearest preset. A Model without a resolution setting is sized to the preset
2139
2136
  * long edge, so an exact size costs what a preset would. Out of the Model's
2140
2137
  * limits it throws rather than rounding to another ratio.
2141
2138
  */
2142
2139
  function falImageSizeFor(config, aspect, resolution) {
2143
2140
  const bounds = config.customImageSize;
2144
- if (bounds === void 0 || FAL_PRESET_ASPECTS.includes(aspect)) return aspectToFalImageSize(aspect);
2141
+ if (bounds === void 0 || !config.supportsResolution && FAL_PRESET_ASPECTS.includes(aspect)) return aspectToFalImageSize(aspect);
2145
2142
  const size = boundedImageSize(aspect, config.supportsResolution ? RESOLUTION_LONG_EDGE[resolution] : FAL_PRESET_LONG_EDGE, bounds);
2146
2143
  if (size === void 0) throw new ImageSizeBoundsError(config.name, aspect, bounds);
2147
2144
  return size ?? aspectToFalImageSize(aspect);
@@ -2154,6 +2151,7 @@ function validateGenerateOptions(options, config, sizeMode, hasEditImages) {
2154
2151
  if (options.editImageUrls !== void 0 && options.editImageUrls.length > 0 && !config.supportsEdit) unsupported(config, "image editing");
2155
2152
  if (options.aspect !== void 0 && sizeMode === "none") unsupported(config, "aspect");
2156
2153
  if (options.resolution !== void 0 && options.resolution !== "2K" && !config.supportsResolution) unsupported(config, "resolution");
2154
+ if (config.autoEditSize === true && hasEditImages && options.aspect === void 0 && options.imageSize === void 0 && options.resolution !== void 0) throw new Error(`${config.name} sizes an edit from its source image; pass an aspect with resolution`);
2157
2155
  if (options.aspect !== void 0 && config.supportedAspects !== void 0 && !config.supportedAspects.includes(options.aspect)) throw new Error(`${config.name} aspect must be one of ${config.supportedAspects.join(", ")}`);
2158
2156
  if (options.resolution !== void 0 && config.supportedResolutions !== void 0 && !config.supportedResolutions.includes(options.resolution)) throw new Error(`${config.name} resolution must be one of ${config.supportedResolutions.join(", ")}`);
2159
2157
  if (options.numImages !== void 0 && options.numImages !== 1 && !config.supportsNumImages) unsupported(config, "numImages");
@@ -2290,8 +2288,7 @@ function buildGenerateBody(options) {
2290
2288
  endpoint
2291
2289
  };
2292
2290
  }
2293
- //#endregion
2294
- //#region src/tool-registry/analysis.ts
2291
+ // src/tool-registry/analysis.ts
2295
2292
  const ANALYSIS_TOOLS = {
2296
2293
  "got-ocr": {
2297
2294
  category: "analysis",
@@ -2405,8 +2402,7 @@ const ANALYSIS_TOOLS = {
2405
2402
  task: "vision moderation"
2406
2403
  }
2407
2404
  };
2408
- //#endregion
2409
- //#region src/tool-registry/assets.ts
2405
+ // src/tool-registry/assets.ts
2410
2406
  const ASSET_TOOLS = {
2411
2407
  "hunyuan3d-v3": {
2412
2408
  category: "3d",
@@ -2724,8 +2720,7 @@ const ASSET_TOOLS = {
2724
2720
  task: "single-image 3D reconstruction"
2725
2721
  }
2726
2722
  };
2727
- //#endregion
2728
- //#region src/tool-registry/background.ts
2723
+ // src/tool-registry/background.ts
2729
2724
  const BACKGROUND_TOOLS = {
2730
2725
  "ben-v2": {
2731
2726
  category: "background",
@@ -3100,8 +3095,7 @@ const BACKGROUND_TOOLS = {
3100
3095
  task: "video enhancement"
3101
3096
  }
3102
3097
  };
3103
- //#endregion
3104
- //#region src/tool-registry/editing.ts
3098
+ // src/tool-registry/editing.ts
3105
3099
  const EDITING_TOOLS = {
3106
3100
  "bria-eraser": {
3107
3101
  category: "erase",
@@ -3372,8 +3366,7 @@ const EDITING_TOOLS = {
3372
3366
  task: "virtual garment try-on"
3373
3367
  }
3374
3368
  };
3375
- //#endregion
3376
- //#region src/tool-registry/structure.ts
3369
+ // src/tool-registry/structure.ts
3377
3370
  const STRUCTURE_TOOLS = {
3378
3371
  "depth-anything": {
3379
3372
  category: "preprocess",
@@ -3765,8 +3758,7 @@ const STRUCTURE_TOOLS = {
3765
3758
  task: "depth preprocessing"
3766
3759
  }
3767
3760
  };
3768
- //#endregion
3769
- //#region src/tools.ts
3761
+ // src/tools.ts
3770
3762
  const FAL_TOOLS = {
3771
3763
  ...ANALYSIS_TOOLS,
3772
3764
  ...ASSET_TOOLS,
@@ -3794,8 +3786,7 @@ function buildFalToolRequest(options) {
3794
3786
  tool
3795
3787
  };
3796
3788
  }
3797
- //#endregion
3798
- //#region src/server-tools.ts
3789
+ // src/server-tools.ts
3799
3790
  /**
3800
3791
  * Tool execution bodies for `FalClient`, both synchronous and queued.
3801
3792
  *
@@ -3925,8 +3916,7 @@ async function runToolQueued(exec, options, onProgress) {
3925
3916
  if (request.isErr()) return err$1(request.error);
3926
3917
  return (await runRequestQueued(exec, request.value, onProgress)).map(({ data }) => data);
3927
3918
  }
3928
- //#endregion
3929
- //#region src/server.ts
3919
+ // src/server.ts
3930
3920
  const FAL_BASE_URL = "https://fal.run";
3931
3921
  const FAL_QUEUE_URL = "https://queue.fal.run";
3932
3922
  const FAL_API_URL = "https://api.fal.ai";
@@ -4243,8 +4233,7 @@ var FalClient = class {
4243
4233
  });
4244
4234
  }
4245
4235
  };
4246
- //#endregion
4247
- //#region src/source-size.ts
4236
+ // src/source-size.ts
4248
4237
  const DATA_URL_REGEX = /^data:[^;,]*;base64,(.*)$/s;
4249
4238
  function decodeBase64(data) {
4250
4239
  try {
@@ -4342,8 +4331,7 @@ function dataUrlImageSize(url) {
4342
4331
  const size = pngSize(bytes) ?? jpegSize(bytes) ?? webpSize(bytes);
4343
4332
  return size !== void 0 && size.width > 0 && size.height > 0 ? size : void 0;
4344
4333
  }
4345
- //#endregion
4346
- //#region src/tool-cost.ts
4334
+ // src/tool-cost.ts
4347
4335
  /**
4348
4336
  * What a tool run costs, before it happens and after.
4349
4337
  *
@@ -4527,8 +4515,8 @@ function sumCosts(costs) {
4527
4515
  unknown
4528
4516
  };
4529
4517
  }
4530
- //#endregion
4531
- //#region src/image-benchmarks.ts
4518
+ // src/image-benchmarks.ts
4519
+ const MEASURED_ON_2_5 = "Measured on GPT Image 2.5. Motif runs GPT Image 2.6, which Arena has not scored yet.";
4532
4520
  const BOARDS = {
4533
4521
  "text-to-image": {
4534
4522
  sunburst: {
@@ -4536,6 +4524,7 @@ const BOARDS = {
4536
4524
  rank: 1,
4537
4525
  uncertainty: 5,
4538
4526
  evaluatedModel: "GPT-Image-2.5 Sunburst",
4527
+ note: MEASURED_ON_2_5,
4539
4528
  preliminary: true,
4540
4529
  votes: 40323
4541
4530
  },
@@ -4544,6 +4533,7 @@ const BOARDS = {
4544
4533
  rank: 2,
4545
4534
  uncertainty: 5,
4546
4535
  evaluatedModel: "GPT-Image-2.5 Flare",
4536
+ note: MEASURED_ON_2_5,
4547
4537
  preliminary: true,
4548
4538
  votes: 39218
4549
4539
  },
@@ -4633,6 +4623,7 @@ const BOARDS = {
4633
4623
  rank: 1,
4634
4624
  uncertainty: 5,
4635
4625
  evaluatedModel: "GPT-Image-2.5 Sunburst",
4626
+ note: MEASURED_ON_2_5,
4636
4627
  preliminary: true
4637
4628
  },
4638
4629
  flare: {
@@ -4640,6 +4631,7 @@ const BOARDS = {
4640
4631
  rank: 2,
4641
4632
  uncertainty: 5,
4642
4633
  evaluatedModel: "GPT-Image-2.5 Flare",
4634
+ note: MEASURED_ON_2_5,
4643
4635
  preliminary: true
4644
4636
  },
4645
4637
  "grok-image-2": {
@@ -4709,8 +4701,7 @@ function imageModelBenchmark(model, task) {
4709
4701
  task
4710
4702
  };
4711
4703
  }
4712
- //#endregion
4713
- //#region src/tasks.ts
4704
+ // src/tasks.ts
4714
4705
  /**
4715
4706
  * The Task registry: every job Motif does, with the Models that can do it
4716
4707
  * ranked best-first and tagged with the Tier each belongs to.
@@ -5587,8 +5578,7 @@ const TASK_IDS = Object.keys(TASKS).filter(isTaskId);
5587
5578
  function isTaskId(value) {
5588
5579
  return Object.hasOwn(TASKS, value);
5589
5580
  }
5590
- //#endregion
5591
- //#region src/tool-parameters.generated.ts
5581
+ // src/tool-parameters.generated.ts
5592
5582
  const FAL_TOOL_PARAMETERS = {
5593
5583
  "ben-v2": [{
5594
5584
  key: "seed",
@@ -7745,8 +7735,7 @@ const FAL_TOOL_PARAMETERS = {
7745
7735
  function falToolParameters(tool) {
7746
7736
  return FAL_TOOL_PARAMETERS[tool] ?? [];
7747
7737
  }
7748
- //#endregion
7749
- //#region src/resolve.ts
7738
+ // src/resolve.ts
7750
7739
  /**
7751
7740
  * Model choice: the one place a Model is chosen, for the CLI and the SDK.
7752
7741
  *
@@ -7781,6 +7770,7 @@ function generationProfile(config) {
7781
7770
  if (config.supportsNegativePrompt === true) capabilities.add("negativePrompt");
7782
7771
  if (config.supportsOutputFormat === true) capabilities.add("outputFormat");
7783
7772
  return {
7773
+ name: config.name,
7784
7774
  aspects: config.supportedAspects,
7785
7775
  capabilities,
7786
7776
  keys: [FAL_KEY],
@@ -7814,6 +7804,7 @@ function toolProfile(tool) {
7814
7804
  const takesReference = "referenceField" in config;
7815
7805
  if (takesReference) capabilities.add("references");
7816
7806
  return {
7807
+ name: config.name,
7817
7808
  capabilities,
7818
7809
  keys: [FAL_KEY],
7819
7810
  maxReferences: takesReference ? 1 : 0
@@ -8054,8 +8045,7 @@ function tierChangesChoice(task, request, environment) {
8054
8045
  }
8055
8046
  return chosen.size > 1;
8056
8047
  }
8057
- //#endregion
8058
- //#region src/task-output.ts
8048
+ // src/task-output.ts
8059
8049
  function isPlainRecord(value) {
8060
8050
  return typeof value === "object" && value !== null && !Array.isArray(value);
8061
8051
  }
@@ -8180,8 +8170,7 @@ function labelUrls(urls, labels) {
8180
8170
  return names !== void 0 && names.length === totals.get(key) ? names[occurrence] : void 0;
8181
8171
  });
8182
8172
  }
8183
- //#endregion
8184
- //#region src/task-plan-pricing.ts
8173
+ // src/task-plan-pricing.ts
8185
8174
  /**
8186
8175
  * What a generation request bills and which body keys the caller owns. Both
8187
8176
  * read the body as sent, `params` included, because fal bills that body.
@@ -8260,8 +8249,7 @@ function checkParamsImageSize(config, model, task, params) {
8260
8249
  task
8261
8250
  });
8262
8251
  }
8263
- //#endregion
8264
- //#region src/task-plan-tools.ts
8252
+ // src/task-plan-tools.ts
8265
8253
  /**
8266
8254
  * Request bodies for fal tool Models: map each TaskInput field onto the key
8267
8255
  * the tool's generated parameter list names, refuse what it doesn't list, and
@@ -8610,8 +8598,7 @@ function toolPlan(task, model, input) {
8610
8598
  queued: "queued" in tool
8611
8599
  });
8612
8600
  }
8613
- //#endregion
8614
- //#region src/task-plan-video.ts
8601
+ // src/task-plan-video.ts
8615
8602
  /**
8616
8603
  * Request bodies for video Models: Kling v3 Pro and Kling v3 Turbo Pro, which
8617
8604
  * take the start frame under different keys and bill by the second.
@@ -8692,8 +8679,7 @@ function turboVideoPlan(task, endpoint, input) {
8692
8679
  queued: true
8693
8680
  });
8694
8681
  }
8695
- //#endregion
8696
- //#region src/task-plan.ts
8682
+ // src/task-plan.ts
8697
8683
  /**
8698
8684
  * `plan()` for the Task client: resolve the Model, build the request body the
8699
8685
  * Model's kind needs, and price it. Pure: no I/O.
@@ -8994,8 +8980,7 @@ function upscalerPlan(task, model, input) {
8994
8980
  queued: false
8995
8981
  });
8996
8982
  }
8997
- //#endregion
8998
- //#region src/task-stream.ts
8983
+ // src/task-stream.ts
8999
8984
  const MAX_EVENT_CHARACTERS = 16777216;
9000
8985
  const JSON_START = /^[[{]/;
9001
8986
  function parsePayload(text) {
@@ -9274,8 +9259,7 @@ function createFrames(requestId) {
9274
9259
  function enforceBufferLimit(length, requestId) {
9275
9260
  if (length > MAX_EVENT_CHARACTERS) throw new MotifError("Stream event exceeds the buffer limit.", 0, "STREAM_EVENT_TOO_LARGE", requestId);
9276
9261
  }
9277
- //#endregion
9278
- //#region src/task-client.ts
9262
+ // src/task-client.ts
9279
9263
  /**
9280
9264
  * The Task client: one function per Task. `createMotif()` returns a client
9281
9265
  * whose `generate`, `erase`, `upscale` and the rest resolve the Model through
@@ -9451,5 +9435,4 @@ function createMotif(config = {}) {
9451
9435
  }
9452
9436
  };
9453
9437
  }
9454
- //#endregion
9455
9438
  export { ACCOUNT_LOCKED, ASPECT_RATIOS, CREATIVE_FIELDS, CREATIVE_TAXONOMY, CreativeOptionError, DEFAULT_TIER, FORMAT_PRESETS, INVALID_OPTION, ImageSizeBoundsError, LOOKS, MotifError, NO_MODEL_AVAILABLE, RESOLUTIONS, TASKS, TASK_IDS, TIERS, createMotif, enrichPrompt, err, formatCost, getFalKeyFromEnv, getLook, isFalAccountLocked, isTaskId, modelProfile, motifEnvSchema, ok, parseMotifEnv, promptWarnings, resolveTask, sanitizePrompt, sumCosts, tierChangesChoice, validateCreativeDirection };
@@ -1,4 +1,8 @@
1
- //#region src/types.d.ts
1
+ /**
2
+ * `@howells/motif-sdk/registry` — the Model and tool tables behind Task
3
+ * resolution, for callers that show or price Models themselves. Most callers
4
+ * want the Task client from the package root instead.
5
+ */
2
6
  /** ─── Model Types ─────────────────────────────────────────────── */
3
7
  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";
4
8
  type Resolution = "0.5K" | "1K" | "2K" | "4K";
@@ -139,22 +143,14 @@ interface ModelConfig {
139
143
  /** Use fal queue submit/status/result even for generate() calls. */
140
144
  useQueue?: boolean;
141
145
  }
142
- //#endregion
143
- //#region src/aspects.d.ts
144
146
  /** Pixel sizes of fal's named `image_size` presets. `auto` has none. */
145
147
  export declare const FAL_PRESET_SIZES: Readonly<Record<string, CustomImageSize>>;
146
- //#endregion
147
- //#region src/cost.d.ts
148
148
  /**
149
149
  * Estimate cost based on model and settings. `editImages` is the number of
150
150
  * input images on an edit, for Models whose edits cost more than generation.
151
151
  */
152
152
  export declare function estimateCost(model: string, resolution?: Resolution, numImages?: number, editImages?: number): number | null;
153
- //#endregion
154
- //#region src/models.d.ts
155
153
  export declare const MODELS: Record<string, ModelConfig>;
156
- //#endregion
157
- //#region src/tool-types.d.ts
158
154
  type FalToolInputKind = "image" | "images" | "video";
159
155
  /**
160
156
  * Machine-readable cost, so `--dry-run` can emit a number rather than the
@@ -313,8 +309,6 @@ interface FalToolConfig {
313
309
  sourceUrl: string;
314
310
  task: string;
315
311
  }
316
- //#endregion
317
- //#region src/tools.d.ts
318
312
  export declare const FAL_TOOLS: {
319
313
  readonly "got-ocr": {
320
314
  readonly category: "analysis";
@@ -1753,5 +1747,4 @@ export declare const FAL_TOOLS: {
1753
1747
  declare const FAL_TOOL_IDS: (keyof typeof FAL_TOOLS)[];
1754
1748
  type FalToolId = (typeof FAL_TOOL_IDS)[number];
1755
1749
  export declare function isFalToolId(tool: string): tool is FalToolId;
1756
- //#endregion
1757
1750
  export type { FalToolConfig, FalToolPrice, ModelConfig };