@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/README.md CHANGED
@@ -34,7 +34,7 @@ Every Task function returns `Result<TaskOutput, MotifError>` from `neverthrow` a
34
34
 
35
35
  ## Curated Models and Tiers
36
36
 
37
- The Task client curates 23 generation Models, with separate rankings for generation and editing. Both follow Arena, read 29 October 2026. `TASKS.generate` and `TASKS.vary` expose the current choices, Tiers and evidence in `basis` and `rankedAt`. A generation with `references` uses the editing ranking.
37
+ The Task client curates 23 generation Models, with separate rankings for generation and editing. Both follow Arena, read 29 October 2026. `TASKS.generate` and `TASKS.vary` expose the current choices, Tiers and evidence in `basis` and `rankedAt`. Sunburst and Flare run GPT Image 2.6; Arena has not scored 2.6 yet, so their benchmarks are the GPT Image 2.5 scores, and each carries a `note` saying so. A generation with `references` uses the editing ranking.
38
38
 
39
39
  | Tier | Generation default | Editing default |
40
40
  | -------------------- | ------------------ | ----------------- |
package/dist/image.d.ts CHANGED
@@ -1,11 +1,25 @@
1
+ /**
2
+ * `@howells/motif-sdk/image` — provider-agnostic image generation + editing.
3
+ *
4
+ * ESM-only subpath export, built on the Vercel AI SDK image interface
5
+ * (`generateImage`, `@ai-sdk/*`). The caller names the provider and model;
6
+ * reuses the SDK's Result convention (`Result<T, MotifError>` — no
7
+ * thrown exceptions). Gemini is reached through OpenRouter.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { createMotifImage } from "@howells/motif-sdk/image";
12
+ *
13
+ * const img = createMotifImage({ defaultProvider: "openrouter" });
14
+ * const r = await img.generate({ model: "gemini-3.1-flash-image", prompt: "a bare concrete wall" });
15
+ * if (r.isOk()) console.log(r.value.images[0].mediaType, r.value.cost);
16
+ * ```
17
+ */
1
18
  import { ImageModel, generateImage } from "ai";
2
19
  import { Result } from "neverthrow";
3
- //#region src/types.d.ts
4
20
  /** ─── Configuration ──────────────────────────────────────────── */
5
21
  /** The network seam: a `fetch`-shaped function. */
6
22
  type FalFetch = (url: string, init: RequestInit) => Promise<Response>;
7
- //#endregion
8
- //#region src/errors.d.ts
9
23
  /**
10
24
  * `MotifError` and its coercion helper.
11
25
  *
@@ -24,8 +38,6 @@ declare class MotifError extends Error {
24
38
  readonly details?: Record<string, unknown>;
25
39
  constructor(message: string, status: number, code?: string, requestId?: string, details?: Record<string, unknown>);
26
40
  }
27
- //#endregion
28
- //#region src/image/types.d.ts
29
41
  /**
30
42
  * Image provider id. All four Phase 1b adapters are implemented
31
43
  * (`openrouter`, `openai`, `replicate`, `fal`); the type keeps an open union tail so
@@ -240,8 +252,6 @@ interface MotifImageClient {
240
252
  */
241
253
  bestOfN: (opts: BestOfNOptions) => Promise<Result<BestOfNResult, MotifError>>;
242
254
  }
243
- //#endregion
244
- //#region src/image/deps.d.ts
245
255
  /** A model resolver: builds an AI SDK `ImageModel` for a (provider, model, key). */
246
256
  type ResolveImageModel = (provider: ImageProviderId, modelId: string, apiKey?: string, fetch?: FalFetch) => ImageModel;
247
257
  /** Internal dependency-injection seam (default: real `generateImage` + adapters). */
@@ -249,8 +259,6 @@ interface MotifImageDeps {
249
259
  generateImage?: typeof generateImage;
250
260
  resolveModel?: ResolveImageModel;
251
261
  }
252
- //#endregion
253
- //#region src/image/provider.d.ts
254
262
  /**
255
263
  * A single image provider. A thin wrapper over the provider's `@ai-sdk/*` image
256
264
  * model, plus the metadata the layer needs to resolve keys and meter spend.
@@ -292,24 +300,14 @@ export declare const PROVIDERS: Record<ImageProviderId, ImageProviderAdapter>;
292
300
  * provider (callers catch this into a `Result.err`).
293
301
  */
294
302
  export declare function getProviderAdapter(provider: ImageProviderId): ImageProviderAdapter;
295
- //#endregion
296
- //#region src/image/openrouter.d.ts
297
303
  /** Env var read for the OpenRouter API key when `apiKey` is not supplied in config. */
298
304
  export declare const OPENROUTER_API_KEY_ENV = "OPENROUTER_API_KEY";
299
- //#endregion
300
- //#region src/image/openai.d.ts
301
305
  /** Env var read for the OpenAI API key when `apiKey` is not supplied in config. */
302
306
  export declare const OPENAI_API_KEY_ENV = "OPENAI_API_KEY";
303
- //#endregion
304
- //#region src/image/replicate.d.ts
305
307
  /** Env var read for the Replicate API token when `apiToken` is not in config. */
306
308
  export declare const REPLICATE_API_KEY_ENV = "REPLICATE_API_TOKEN";
307
- //#endregion
308
- //#region src/image/fal.d.ts
309
309
  /** Env var read for the fal key when `apiKey` is not supplied in config. */
310
310
  export declare const FAL_API_KEY_ENV = "FAL_KEY";
311
- //#endregion
312
- //#region src/image/cost.d.ts
313
311
  /**
314
312
  * Extract a provider-reported total cost from a `generateImage` result's
315
313
  * `providerMetadata`, if the provider surfaces a numeric `cost`. The shape is
@@ -323,8 +321,6 @@ export declare function providerPricePerImageUsd(provider: ImageProviderId, mode
323
321
  * then the static table (× image count), then unknown.
324
322
  */
325
323
  export declare function costForImages(provider: ImageProviderId, modelId: string, providerMetadata: unknown, imageCount: number): ImageCost;
326
- //#endregion
327
- //#region src/image/index.d.ts
328
324
  /**
329
325
  * Create a provider-agnostic image client.
330
326
  *
@@ -333,5 +329,4 @@ export declare function costForImages(provider: ImageProviderId, modelId: string
333
329
  * and the built-in provider adapters; tests inject fakes here to run offline.
334
330
  */
335
331
  export declare function createMotifImage(config?: MotifImageConfig, deps?: MotifImageDeps): MotifImageClient;
336
- //#endregion
337
332
  export type { BestOfNOptions, BestOfNResult, EditImageOptions, GenerateImageOptions, ImageCost, ImageCostSource, ImageJudge, ImageProviderAdapter, ImageProviderId, MotifImageClient, MotifImageConfig, MotifImageFile, MotifImageResult };
package/dist/image.js CHANGED
@@ -3,7 +3,7 @@ import { err, ok } from "neverthrow";
3
3
  import { createFal } from "@ai-sdk/fal";
4
4
  import { createOpenAI } from "@ai-sdk/openai";
5
5
  import { createReplicate } from "@ai-sdk/replicate";
6
- //#region src/errors.ts
6
+ // src/errors.ts
7
7
  /**
8
8
  * `MotifError` and its coercion helper.
9
9
  *
@@ -49,8 +49,7 @@ function falHttpError(status, body, requestId) {
49
49
  if (isFalAccountLocked(status, body)) return new MotifError(`fal account is locked because it is out of credit (fal said: ${body})`, status, ACCOUNT_LOCKED, requestId);
50
50
  return new MotifError(`Request failed: ${status} ${body}`, status, void 0, requestId);
51
51
  }
52
- //#endregion
53
- //#region src/models.ts
52
+ // src/models.ts
54
53
  const AA_IMAGE_LEADERBOARD_SNAPSHOT = "2026-10-07";
55
54
  const AA_IMAGE_SOURCES = ["https://artificialanalysis.ai/image/leaderboard/text-to-image", "https://artificialanalysis.ai/image/leaderboard/editing"];
56
55
  const FAL_PRICING_CHECKED_AT = "2026-05-12";
@@ -120,7 +119,7 @@ const MODELS = {
120
119
  "xhigh",
121
120
  "max"
122
121
  ],
123
- supportsResolution: false,
122
+ supportsResolution: true,
124
123
  supportsSyncMode: true,
125
124
  type: "generation",
126
125
  useQueue: true
@@ -187,7 +186,7 @@ const MODELS = {
187
186
  "xhigh",
188
187
  "max"
189
188
  ],
190
- supportsResolution: false,
189
+ supportsResolution: true,
191
190
  supportsSyncMode: true,
192
191
  type: "generation",
193
192
  useQueue: true
@@ -1219,8 +1218,7 @@ const MODELS = {
1219
1218
  "ideogram3-transparent",
1220
1219
  "recraft41"
1221
1220
  ].filter((id) => MODELS[id]?.supportsEdit === true);
1222
- //#endregion
1223
- //#region src/capabilities.ts
1221
+ // src/capabilities.ts
1224
1222
  /** Generation options a model can refuse, with the registry test for each. */
1225
1223
  const OPTION_CAPABILITIES = {
1226
1224
  aspect: (config) => (config.sizeMode ?? "aspect_ratio") !== "none",
@@ -1377,8 +1375,7 @@ Object.keys(OPTION_CAPABILITIES).filter(isModelOption);
1377
1375
  }
1378
1376
  ]
1379
1377
  }).look;
1380
- //#endregion
1381
- //#region src/tool-registry/analysis.ts
1378
+ // src/tool-registry/analysis.ts
1382
1379
  const ANALYSIS_TOOLS = {
1383
1380
  "got-ocr": {
1384
1381
  category: "analysis",
@@ -1492,8 +1489,7 @@ const ANALYSIS_TOOLS = {
1492
1489
  task: "vision moderation"
1493
1490
  }
1494
1491
  };
1495
- //#endregion
1496
- //#region src/tool-registry/assets.ts
1492
+ // src/tool-registry/assets.ts
1497
1493
  const ASSET_TOOLS = {
1498
1494
  "hunyuan3d-v3": {
1499
1495
  category: "3d",
@@ -1811,8 +1807,7 @@ const ASSET_TOOLS = {
1811
1807
  task: "single-image 3D reconstruction"
1812
1808
  }
1813
1809
  };
1814
- //#endregion
1815
- //#region src/tool-registry/background.ts
1810
+ // src/tool-registry/background.ts
1816
1811
  const BACKGROUND_TOOLS = {
1817
1812
  "ben-v2": {
1818
1813
  category: "background",
@@ -2187,8 +2182,7 @@ const BACKGROUND_TOOLS = {
2187
2182
  task: "video enhancement"
2188
2183
  }
2189
2184
  };
2190
- //#endregion
2191
- //#region src/tool-registry/editing.ts
2185
+ // src/tool-registry/editing.ts
2192
2186
  const EDITING_TOOLS = {
2193
2187
  "bria-eraser": {
2194
2188
  category: "erase",
@@ -2459,8 +2453,7 @@ const EDITING_TOOLS = {
2459
2453
  task: "virtual garment try-on"
2460
2454
  }
2461
2455
  };
2462
- //#endregion
2463
- //#region src/tool-registry/structure.ts
2456
+ // src/tool-registry/structure.ts
2464
2457
  const STRUCTURE_TOOLS = {
2465
2458
  "depth-anything": {
2466
2459
  category: "preprocess",
@@ -2852,8 +2845,7 @@ const STRUCTURE_TOOLS = {
2852
2845
  task: "depth preprocessing"
2853
2846
  }
2854
2847
  };
2855
- //#endregion
2856
- //#region src/tools.ts
2848
+ // src/tools.ts
2857
2849
  const FAL_TOOLS = {
2858
2850
  ...ANALYSIS_TOOLS,
2859
2851
  ...ASSET_TOOLS,
@@ -2862,8 +2854,7 @@ const FAL_TOOLS = {
2862
2854
  ...STRUCTURE_TOOLS
2863
2855
  };
2864
2856
  Object.keys(FAL_TOOLS);
2865
- //#endregion
2866
- //#region src/image/fetch.ts
2857
+ // src/image/fetch.ts
2867
2858
  function urlOf(input) {
2868
2859
  if (typeof input === "string") return input;
2869
2860
  return input instanceof URL ? input.href : input.url;
@@ -2873,8 +2864,7 @@ function toProviderFetch(fetch) {
2873
2864
  if (fetch === void 0) return {};
2874
2865
  return { fetch: async (input, init) => await fetch(urlOf(input), init ?? {}) };
2875
2866
  }
2876
- //#endregion
2877
- //#region src/image/fal.ts
2867
+ // src/image/fal.ts
2878
2868
  /**
2879
2869
  * fal provider adapter.
2880
2870
  *
@@ -2960,8 +2950,7 @@ const falAdapter = {
2960
2950
  resolveModel: resolveModel$3,
2961
2951
  priceUsdByModel: FAL_IMAGE_PRICE_USD
2962
2952
  };
2963
- //#endregion
2964
- //#region src/image/openai.ts
2953
+ // src/image/openai.ts
2965
2954
  /**
2966
2955
  * OpenAI (gpt-image) provider adapter.
2967
2956
  *
@@ -3007,8 +2996,7 @@ const openaiAdapter = {
3007
2996
  resolveModel: resolveModel$2,
3008
2997
  priceUsdByModel: OPENAI_IMAGE_PRICE_USD
3009
2998
  };
3010
- //#endregion
3011
- //#region src/image/openrouter.ts
2999
+ // src/image/openrouter.ts
3012
3000
  /** Env var read for the OpenRouter API key when `apiKey` is not supplied in config. */
3013
3001
  const OPENROUTER_API_KEY_ENV = "OPENROUTER_API_KEY";
3014
3002
  const OPENROUTER_IMAGES_URL = "https://openrouter.ai/api/v1/images";
@@ -3171,8 +3159,7 @@ const openrouterAdapter = {
3171
3159
  resolveModel: resolveModel$1,
3172
3160
  priceUsdByModel: OPENROUTER_IMAGE_PRICE_USD
3173
3161
  };
3174
- //#endregion
3175
- //#region src/image/replicate.ts
3162
+ // src/image/replicate.ts
3176
3163
  /**
3177
3164
  * Replicate provider adapter.
3178
3165
  *
@@ -3207,8 +3194,7 @@ function resolveModel(modelId, apiKey, fetch) {
3207
3194
  ...toProviderFetch(fetch)
3208
3195
  }).image(modelId);
3209
3196
  }
3210
- //#endregion
3211
- //#region src/image/provider.ts
3197
+ // src/image/provider.ts
3212
3198
  /**
3213
3199
  * The provider registry. Every id in {@link ImageProviderId}'s closed part maps
3214
3200
  * to its adapter; the open union tail means a lookup can still miss, so access
@@ -3234,8 +3220,7 @@ function getProviderAdapter(provider) {
3234
3220
  if (adapter === void 0) throw new MotifError(`Unsupported image provider: ${provider}`, 0);
3235
3221
  return adapter;
3236
3222
  }
3237
- //#endregion
3238
- //#region src/image/cost.ts
3223
+ // src/image/cost.ts
3239
3224
  /**
3240
3225
  * Per-call cost tracking for the image layer.
3241
3226
  *
@@ -3305,8 +3290,7 @@ function costForCalls(provider, modelId, callMetadata, imageCount) {
3305
3290
  source: "unknown"
3306
3291
  };
3307
3292
  }
3308
- //#endregion
3309
- //#region src/image/index.ts
3293
+ // src/image/index.ts
3310
3294
  /**
3311
3295
  * `@howells/motif-sdk/image` — provider-agnostic image generation + editing.
3312
3296
  *
@@ -3578,5 +3562,4 @@ function toMotifError(error) {
3578
3562
  if (isFalAccountLocked(status, body)) return falHttpError(status, body);
3579
3563
  return new MotifError(message, status, code);
3580
3564
  }
3581
- //#endregion
3582
3565
  export { FAL_API_KEY_ENV, OPENAI_API_KEY_ENV, OPENROUTER_API_KEY_ENV, PROVIDERS, REPLICATE_API_KEY_ENV, costForImages, costFromProviderMetadata, createMotifImage, getProviderAdapter, providerPricePerImageUsd };