@howells/motif-sdk 1.3.1 → 2.0.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
@@ -171,12 +171,29 @@ Four providers are implemented, each reading its own API key from the environmen
171
171
  | Provider | Env var | Notes |
172
172
  | --- | --- | --- |
173
173
  | `google` | `GOOGLE_GENERATIVE_AI_API_KEY` | Default provider; Gemini gen + edit |
174
- | `openai` | `OPENAI_API_KEY` | gpt-image-1 |
174
+ | `openai` | `OPENAI_API_KEY` | GPT Image 2.5 Flare (fast/balanced), Sunburst (quality/hero) |
175
175
  | `replicate` | `REPLICATE_API_TOKEN` | flux-1.1-pro-ultra |
176
176
  | `fal` | `FAL_KEY` | fal-hosted adapter |
177
177
 
178
178
  `generate()` and `edit()` accept `tier` (`"fast" | "balanced" | "quality" | "hero"`) to resolve a model per provider, or an explicit `model` id. Every result carries a normalized per-call `cost: { usd, source }`.
179
179
 
180
+ For OpenAI, `fast` and `balanced` (the default tier) select `gpt-image-2.5-flare`; `quality` and `hero` select `gpt-image-2.5-sunburst`. This updates the previous OpenAI tier default of `gpt-image-1`; pass that explicit model to retain it. Both new models support generation and multi-image editing:
181
+
182
+ ```ts
183
+ const image = createMotifImage({ defaultProvider: "openai" });
184
+ const generated = await image.generate({
185
+ prompt: "A ceramic vase in soft window light",
186
+ model: "gpt-image-2.5-flare",
187
+ });
188
+ const refined = await image.edit({
189
+ images: [referenceBytes],
190
+ instruction: "Change only the vase glaze to deep green",
191
+ model: "gpt-image-2.5-sunburst",
192
+ });
193
+ ```
194
+
195
+ These models use token-based billing. Motif has no static per-image estimate for them, so `cost` is `{ usd: 0, source: "unknown" }` unless the provider supplies a cost; this does not mean generation is free. See the official [Flare](https://developers.openai.com/api/docs/models/gpt-image-2.5-flare) and [Sunburst](https://developers.openai.com/api/docs/models/gpt-image-2.5-sunburst) model pages. The installed OpenAI adapter accepts `low`, `medium`, `high`, and `auto` quality; the new `xhigh` and `max` settings require a future adapter update. The fal-backed CLI and `FalClient` expose these models as `flare` and `sunburst`. Fal supports `xhigh` and `max`, up to 16 edit references, masks and transparent backgrounds. Fal generation estimates are `null` (metered), including `estimateCost()` and queued jobs.
196
+
180
197
  ### Best-of-N with an injectable judge
181
198
 
182
199
  `bestOfN()` generates `n` candidates in parallel and picks a winner. It reuses the same options as `generate()` (text→image) or `edit()` (pass `images` for the edit path), plus `n` and an optional `judge`. When a `seed` is given each candidate uses `seed + index`, so the N vary. The judge is a caller-provided function — the layer takes no text-client dependency, so it pairs well with `@howells/ai`'s vision client but does not require it. Omit the judge and candidate 0 wins.
package/dist/image.d.ts CHANGED
@@ -304,7 +304,10 @@ export declare const GOOGLE_TIER_MODELS: Readonly<Record<ImageTier, string>>;
304
304
  export declare const GOOGLE_API_KEY_ENV = "GOOGLE_GENERATIVE_AI_API_KEY";
305
305
  //#endregion
306
306
  //#region src/image/openai.d.ts
307
- /** Tier → OpenAI image model id (all tiers → the single gpt-image model). */
307
+ /**
308
+ * Flare favors speed for everyday generation; Sunburst favors editing precision.
309
+ * Explicit model ids still override tiers, including older GPT Image models.
310
+ */
308
311
  export declare const OPENAI_TIER_MODELS: Readonly<Record<ImageTier, string>>;
309
312
  /** Env var read for the OpenAI API key when `apiKey` is not supplied in config. */
310
313
  export declare const OPENAI_API_KEY_ENV = "OPENAI_API_KEY";
package/dist/image.js CHANGED
@@ -10,6 +10,64 @@ const AA_IMAGE_SOURCES = ["https://artificialanalysis.ai/image/leaderboard/text-
10
10
  const FAL_PRICING_CHECKED_AT = "2026-05-12";
11
11
  const FAL_PRICING_CHECKED_JUL_2026 = "2026-07-11";
12
12
  const MODELS = {
13
+ flare: {
14
+ editEndpoint: "openai/gpt-image-2.5/flare/edit",
15
+ endpoint: "openai/gpt-image-2.5/flare/text-to-image",
16
+ maxReferenceImages: 16,
17
+ name: "GPT Image 2.5 Flare",
18
+ pricing: "Token-based: text $5/M input, $1.25/M cached, $10/M output; image $8/M input, $2/M cached, $30/M output",
19
+ pricePerImageUsd: null,
20
+ sizeMode: "image_size_enum",
21
+ supportsAspect: true,
22
+ supportsBackground: true,
23
+ supportsEdit: true,
24
+ supportsMaskImage: true,
25
+ maskImageField: "mask_url",
26
+ supportsNumImages: true,
27
+ supportsOutputFormat: true,
28
+ supportsQuality: true,
29
+ supportedQualities: [
30
+ "auto",
31
+ "low",
32
+ "medium",
33
+ "high",
34
+ "xhigh",
35
+ "max"
36
+ ],
37
+ supportsResolution: false,
38
+ supportsSyncMode: true,
39
+ type: "generation",
40
+ useQueue: true
41
+ },
42
+ sunburst: {
43
+ editEndpoint: "openai/gpt-image-2.5/sunburst/edit",
44
+ endpoint: "openai/gpt-image-2.5/sunburst/text-to-image",
45
+ maxReferenceImages: 16,
46
+ name: "GPT Image 2.5 Sunburst",
47
+ pricing: "Token-based: text $5/M input, $1.25/M cached, $10/M output; image $8/M input, $2/M cached, $30/M output",
48
+ pricePerImageUsd: null,
49
+ sizeMode: "image_size_enum",
50
+ supportsAspect: true,
51
+ supportsBackground: true,
52
+ supportsEdit: true,
53
+ supportsMaskImage: true,
54
+ maskImageField: "mask_url",
55
+ supportsNumImages: true,
56
+ supportsOutputFormat: true,
57
+ supportsQuality: true,
58
+ supportedQualities: [
59
+ "auto",
60
+ "low",
61
+ "medium",
62
+ "high",
63
+ "xhigh",
64
+ "max"
65
+ ],
66
+ supportsResolution: false,
67
+ supportsSyncMode: true,
68
+ type: "generation",
69
+ useQueue: true
70
+ },
13
71
  gpt2: {
14
72
  benchmark: {
15
73
  artificialAnalysis: {
@@ -1104,6 +1162,8 @@ const MODELS = {
1104
1162
  }
1105
1163
  };
1106
1164
  [
1165
+ "flare",
1166
+ "sunburst",
1107
1167
  "gpt2",
1108
1168
  "gpt",
1109
1169
  "banana2",
@@ -2573,18 +2633,14 @@ const googleAdapter = {
2573
2633
  * invokes `model.doGenerate`.
2574
2634
  */
2575
2635
  /**
2576
- * OpenAI's image API currently exposes a single gpt-image model, so every tier
2577
- * maps to `gpt-image-1`. Tiers will later differ by `quality`
2578
- * (low/medium/high/auto) passed via `providerOptions.openai` (Phase 1c); until
2579
- * then the tier only selects this one model.
2636
+ * Flare favors speed for everyday generation; Sunburst favors editing precision.
2637
+ * Explicit model ids still override tiers, including older GPT Image models.
2580
2638
  */
2581
- const OPENAI_MODEL = "gpt-image-1";
2582
- /** Tier → OpenAI image model id (all tiers → the single gpt-image model). */
2583
2639
  const OPENAI_TIER_MODELS = {
2584
- fast: OPENAI_MODEL,
2585
- balanced: OPENAI_MODEL,
2586
- quality: OPENAI_MODEL,
2587
- hero: OPENAI_MODEL
2640
+ fast: "gpt-image-2.5-flare",
2641
+ balanced: "gpt-image-2.5-flare",
2642
+ quality: "gpt-image-2.5-sunburst",
2643
+ hero: "gpt-image-2.5-sunburst"
2588
2644
  };
2589
2645
  /** Env var read for the OpenAI API key when `apiKey` is not supplied in config. */
2590
2646
  const OPENAI_API_KEY_ENV = "OPENAI_API_KEY";
@@ -2597,6 +2653,11 @@ const OPENAI_API_KEY_ENV = "OPENAI_API_KEY";
2597
2653
  * Source: https://platform.openai.com/docs/pricing (image generation)
2598
2654
  * Sanity anchor: the Phase 0 benchmark measured gpt-image direct at $0.042
2599
2655
  * (vs $0.133 via fal — see docs/design/provider-agnostic-image-layer.md §10).
2656
+ *
2657
+ * GPT Image 2.5 is token-priced, with no published per-image estimate. Leave
2658
+ * these models absent so cost remains unknown unless supplied by the provider.
2659
+ * https://developers.openai.com/api/docs/models/gpt-image-2.5-flare
2660
+ * https://developers.openai.com/api/docs/models/gpt-image-2.5-sunburst
2600
2661
  */
2601
2662
  const OPENAI_IMAGE_PRICE_USD = { "gpt-image-1": .042 };
2602
2663
  /**
package/dist/index.cjs CHANGED
@@ -112,6 +112,64 @@ const AA_IMAGE_SOURCES = ["https://artificialanalysis.ai/image/leaderboard/text-
112
112
  const FAL_PRICING_CHECKED_AT = "2026-05-12";
113
113
  const FAL_PRICING_CHECKED_JUL_2026 = "2026-07-11";
114
114
  const MODELS = {
115
+ flare: {
116
+ editEndpoint: "openai/gpt-image-2.5/flare/edit",
117
+ endpoint: "openai/gpt-image-2.5/flare/text-to-image",
118
+ maxReferenceImages: 16,
119
+ name: "GPT Image 2.5 Flare",
120
+ pricing: "Token-based: text $5/M input, $1.25/M cached, $10/M output; image $8/M input, $2/M cached, $30/M output",
121
+ pricePerImageUsd: null,
122
+ sizeMode: "image_size_enum",
123
+ supportsAspect: true,
124
+ supportsBackground: true,
125
+ supportsEdit: true,
126
+ supportsMaskImage: true,
127
+ maskImageField: "mask_url",
128
+ supportsNumImages: true,
129
+ supportsOutputFormat: true,
130
+ supportsQuality: true,
131
+ supportedQualities: [
132
+ "auto",
133
+ "low",
134
+ "medium",
135
+ "high",
136
+ "xhigh",
137
+ "max"
138
+ ],
139
+ supportsResolution: false,
140
+ supportsSyncMode: true,
141
+ type: "generation",
142
+ useQueue: true
143
+ },
144
+ sunburst: {
145
+ editEndpoint: "openai/gpt-image-2.5/sunburst/edit",
146
+ endpoint: "openai/gpt-image-2.5/sunburst/text-to-image",
147
+ maxReferenceImages: 16,
148
+ name: "GPT Image 2.5 Sunburst",
149
+ pricing: "Token-based: text $5/M input, $1.25/M cached, $10/M output; image $8/M input, $2/M cached, $30/M output",
150
+ pricePerImageUsd: null,
151
+ sizeMode: "image_size_enum",
152
+ supportsAspect: true,
153
+ supportsBackground: true,
154
+ supportsEdit: true,
155
+ supportsMaskImage: true,
156
+ maskImageField: "mask_url",
157
+ supportsNumImages: true,
158
+ supportsOutputFormat: true,
159
+ supportsQuality: true,
160
+ supportedQualities: [
161
+ "auto",
162
+ "low",
163
+ "medium",
164
+ "high",
165
+ "xhigh",
166
+ "max"
167
+ ],
168
+ supportsResolution: false,
169
+ supportsSyncMode: true,
170
+ type: "generation",
171
+ useQueue: true
172
+ },
115
173
  gpt2: {
116
174
  benchmark: {
117
175
  artificialAnalysis: {
@@ -1206,6 +1264,8 @@ const MODELS = {
1206
1264
  }
1207
1265
  };
1208
1266
  const GENERATION_MODELS = [
1267
+ "flare",
1268
+ "sunburst",
1209
1269
  "gpt2",
1210
1270
  "gpt",
1211
1271
  "banana2",
@@ -1288,6 +1348,7 @@ const IDEOGRAM_STYLES = [
1288
1348
  /** Estimate cost based on model and settings */
1289
1349
  function estimateCost(model, resolution, numImages = 1) {
1290
1350
  const configuredPrice = MODELS[model]?.pricePerImageUsd;
1351
+ if (configuredPrice === null) return null;
1291
1352
  if (configuredPrice !== void 0) {
1292
1353
  if ((model === "banana" || model === "gemini3") && resolution === "4K") return configuredPrice * 2 * numImages;
1293
1354
  if (model === "seedream5" && (resolution === "2K" || resolution === "4K")) return configuredPrice * 2 * numImages;
@@ -1513,6 +1574,15 @@ function validateGenerateOptions(options, config, sizeMode, hasEditImages) {
1513
1574
  if (options.seed !== void 0 && config.supportsSeed !== true) unsupported(config, "seed");
1514
1575
  if (options.outputFormat !== void 0 && config.supportsOutputFormat !== true) unsupported(config, "outputFormat");
1515
1576
  if (options.quality !== void 0 && config.supportsQuality !== true) unsupported(config, "quality");
1577
+ if (options.quality !== void 0) {
1578
+ const qualities = config.supportedQualities ?? [
1579
+ "auto",
1580
+ "low",
1581
+ "medium",
1582
+ "high"
1583
+ ];
1584
+ if (!qualities.includes(options.quality)) throw new Error(`${config.name} quality must be one of ${qualities.join(", ")}`);
1585
+ }
1516
1586
  if (options.negativePrompt !== void 0 && config.supportsNegativePrompt !== true) unsupported(config, "negativePrompt");
1517
1587
  if (options.style !== void 0 && config.supportsStyle !== true) unsupported(config, "style");
1518
1588
  if (options.syncMode !== void 0 && config.supportsSyncMode !== true) unsupported(config, "syncMode");
@@ -1631,7 +1701,7 @@ function buildGenerateBody(options) {
1631
1701
  body.image_urls = editImages;
1632
1702
  if (inputFidelity) body.input_fidelity = inputFidelity;
1633
1703
  }
1634
- if (maskImageUrl !== void 0 && maskImageUrl !== "" && config.supportsMaskImage === true) body.mask_image_url = maskImageUrl;
1704
+ if (maskImageUrl !== void 0 && maskImageUrl !== "" && config.supportsMaskImage === true) body[config.maskImageField ?? "mask_image_url"] = maskImageUrl;
1635
1705
  return {
1636
1706
  body,
1637
1707
  endpoint
package/dist/index.d.cts CHANGED
@@ -145,7 +145,7 @@ type Resolution = "0.5K" | "1K" | "2K" | "4K";
145
145
  type ModelType = "generation" | "utility" | "video";
146
146
  type ImageOutputFormat = "jpeg" | "png" | "webp";
147
147
  type BackgroundMode = "auto" | "transparent" | "opaque";
148
- type ImageQuality = "auto" | "low" | "medium" | "high";
148
+ type ImageQuality = "auto" | "low" | "medium" | "high" | "xhigh" | "max";
149
149
  type ThinkingLevel = "minimal" | "high";
150
150
  type GptImageSize = "auto" | "1024x1024" | "1536x1024" | "1024x1536";
151
151
  type FalImageSizePreset = "auto" | "square_hd" | "square" | "portrait_4_3" | "portrait_16_9" | "landscape_4_3" | "landscape_16_9";
@@ -201,7 +201,8 @@ interface ModelConfig {
201
201
  maxReferenceImages?: number;
202
202
  name: string;
203
203
  pricing: string;
204
- pricePerImageUsd?: number;
204
+ /** Null means token-metered with no per-image estimate. */
205
+ pricePerImageUsd?: number | null;
205
206
  /** How this model accepts dimensions (default: "aspect_ratio") */
206
207
  sizeMode?: SizeMode;
207
208
  supportsAspect: boolean;
@@ -215,11 +216,13 @@ interface ModelConfig {
215
216
  supportsInferenceSteps?: boolean;
216
217
  supportsLimitGenerations?: boolean;
217
218
  supportsMaskImage?: boolean;
219
+ maskImageField?: "mask_image_url" | "mask_url";
218
220
  supportsNegativePrompt?: boolean;
219
221
  supportsNumImages: boolean;
220
222
  supportsOutputFormat?: boolean;
221
223
  supportedOutputFormats?: readonly ImageOutputFormat[];
222
224
  supportsQuality?: boolean;
225
+ supportedQualities?: readonly ImageQuality[];
223
226
  supportsRaw?: boolean;
224
227
  supportsRenderingSpeed?: boolean;
225
228
  supportsResolution: boolean;
@@ -373,7 +376,7 @@ interface MotifResponse {
373
376
  /** ─── Queue Types ────────────────────────────────────────────── */
374
377
  interface QueuedJob {
375
378
  endpoint: string;
376
- estimatedCost: number;
379
+ estimatedCost: number | null;
377
380
  requestId: string;
378
381
  }
379
382
  interface JobStatus {
@@ -428,7 +431,7 @@ export declare const FORMAT_PRESETS: Record<string, {
428
431
  //#endregion
429
432
  //#region src/cost.d.ts
430
433
  /** Estimate cost based on model and settings */
431
- export declare function estimateCost(model: string, resolution?: Resolution, numImages?: number): number;
434
+ export declare function estimateCost(model: string, resolution?: Resolution, numImages?: number): number | null;
432
435
  /** Estimate cost for video generation */
433
436
  export declare function estimateVideoCost(durationSeconds?: number, generateAudio?: boolean): number;
434
437
  //#endregion
@@ -485,9 +488,9 @@ export declare const VIDEO_IMAGE_TO_VIDEO_TOP_15: LeaderboardSnapshot;
485
488
  //#endregion
486
489
  //#region src/models.d.ts
487
490
  export declare const MODELS: Record<string, ModelConfig>;
488
- export declare const GENERATION_MODELS: readonly ["gpt2", "gpt", "banana2", "banana", "gemini", "gemini3", "seedream4", "seedream45", "seedream5", "seedream5-lite", "flux2-max", "flux2-pro", "flux2-flex", "flux2-dev", "flux2-turbo", "flux", "flux-fast", "recraft", "recraft4", "ideogram", "ideogram4", "grok-image", "qwen", "qwen3"];
491
+ export declare const GENERATION_MODELS: readonly ["flare", "sunburst", "gpt2", "gpt", "banana2", "banana", "gemini", "gemini3", "seedream4", "seedream45", "seedream5", "seedream5-lite", "flux2-max", "flux2-pro", "flux2-flex", "flux2-dev", "flux2-turbo", "flux", "flux-fast", "recraft", "recraft4", "ideogram", "ideogram4", "grok-image", "qwen", "qwen3"];
489
492
  /** Models whose fal endpoints support image editing (vary/edit flows). */
490
- export declare const EDIT_CAPABLE_MODELS: ("banana" | "banana2" | "flux" | "flux-fast" | "flux2-dev" | "flux2-flex" | "flux2-max" | "flux2-pro" | "flux2-turbo" | "gemini" | "gemini3" | "gpt" | "gpt2" | "grok-image" | "ideogram" | "ideogram4" | "qwen" | "qwen3" | "recraft" | "recraft4" | "seedream4" | "seedream45" | "seedream5" | "seedream5-lite")[];
493
+ export declare const EDIT_CAPABLE_MODELS: ("banana" | "banana2" | "flare" | "flux" | "flux-fast" | "flux2-dev" | "flux2-flex" | "flux2-max" | "flux2-pro" | "flux2-turbo" | "gemini" | "gemini3" | "gpt" | "gpt2" | "grok-image" | "ideogram" | "ideogram4" | "qwen" | "qwen3" | "recraft" | "recraft4" | "seedream4" | "seedream45" | "seedream5" | "seedream5-lite" | "sunburst")[];
491
494
  export declare const UTILITY_MODELS: readonly ["clarity", "crystal", "rmbg", "bria"];
492
495
  export declare const VIDEO_MODELS: readonly ["kling"];
493
496
  type GenerationModelName = (typeof GENERATION_MODELS)[number];
@@ -608,7 +611,7 @@ export declare class FalClient {
608
611
  */
609
612
  deletePayloads(requestId: string): Promise<Result$1<void, MotifError>>;
610
613
  /** Estimate cost for a generation (no API call). */
611
- estimateCost(model: string, resolution?: Resolution, numImages?: number): number;
614
+ estimateCost(model: string, resolution?: Resolution, numImages?: number): number | null;
612
615
  /** Build the fal.ai request body without sending it. */
613
616
  buildRequestBody(options: GenerateOptions): {
614
617
  endpoint: string;
@@ -617,7 +620,7 @@ export declare class FalClient {
617
620
  /** Model registry. */
618
621
  get models(): Record<string, ModelConfig>;
619
622
  /** Generation model keys. */
620
- get generationModels(): readonly ["gpt2", "gpt", "banana2", "banana", "gemini", "gemini3", "seedream4", "seedream45", "seedream5", "seedream5-lite", "flux2-max", "flux2-pro", "flux2-flex", "flux2-dev", "flux2-turbo", "flux", "flux-fast", "recraft", "recraft4", "ideogram", "ideogram4", "grok-image", "qwen", "qwen3"];
623
+ get generationModels(): readonly ["flare", "sunburst", "gpt2", "gpt", "banana2", "banana", "gemini", "gemini3", "seedream4", "seedream45", "seedream5", "seedream5-lite", "flux2-max", "flux2-pro", "flux2-flex", "flux2-dev", "flux2-turbo", "flux", "flux-fast", "recraft", "recraft4", "ideogram", "ideogram4", "grok-image", "qwen", "qwen3"];
621
624
  /** Utility model keys. */
622
625
  get utilityModels(): readonly ["clarity", "crystal", "rmbg", "bria"];
623
626
  /** Registered fal utility/tool endpoints. */
package/dist/index.d.ts CHANGED
@@ -145,7 +145,7 @@ type Resolution = "0.5K" | "1K" | "2K" | "4K";
145
145
  type ModelType = "generation" | "utility" | "video";
146
146
  type ImageOutputFormat = "jpeg" | "png" | "webp";
147
147
  type BackgroundMode = "auto" | "transparent" | "opaque";
148
- type ImageQuality = "auto" | "low" | "medium" | "high";
148
+ type ImageQuality = "auto" | "low" | "medium" | "high" | "xhigh" | "max";
149
149
  type ThinkingLevel = "minimal" | "high";
150
150
  type GptImageSize = "auto" | "1024x1024" | "1536x1024" | "1024x1536";
151
151
  type FalImageSizePreset = "auto" | "square_hd" | "square" | "portrait_4_3" | "portrait_16_9" | "landscape_4_3" | "landscape_16_9";
@@ -201,7 +201,8 @@ interface ModelConfig {
201
201
  maxReferenceImages?: number;
202
202
  name: string;
203
203
  pricing: string;
204
- pricePerImageUsd?: number;
204
+ /** Null means token-metered with no per-image estimate. */
205
+ pricePerImageUsd?: number | null;
205
206
  /** How this model accepts dimensions (default: "aspect_ratio") */
206
207
  sizeMode?: SizeMode;
207
208
  supportsAspect: boolean;
@@ -215,11 +216,13 @@ interface ModelConfig {
215
216
  supportsInferenceSteps?: boolean;
216
217
  supportsLimitGenerations?: boolean;
217
218
  supportsMaskImage?: boolean;
219
+ maskImageField?: "mask_image_url" | "mask_url";
218
220
  supportsNegativePrompt?: boolean;
219
221
  supportsNumImages: boolean;
220
222
  supportsOutputFormat?: boolean;
221
223
  supportedOutputFormats?: readonly ImageOutputFormat[];
222
224
  supportsQuality?: boolean;
225
+ supportedQualities?: readonly ImageQuality[];
223
226
  supportsRaw?: boolean;
224
227
  supportsRenderingSpeed?: boolean;
225
228
  supportsResolution: boolean;
@@ -373,7 +376,7 @@ interface MotifResponse {
373
376
  /** ─── Queue Types ────────────────────────────────────────────── */
374
377
  interface QueuedJob {
375
378
  endpoint: string;
376
- estimatedCost: number;
379
+ estimatedCost: number | null;
377
380
  requestId: string;
378
381
  }
379
382
  interface JobStatus {
@@ -428,7 +431,7 @@ export declare const FORMAT_PRESETS: Record<string, {
428
431
  //#endregion
429
432
  //#region src/cost.d.ts
430
433
  /** Estimate cost based on model and settings */
431
- export declare function estimateCost(model: string, resolution?: Resolution, numImages?: number): number;
434
+ export declare function estimateCost(model: string, resolution?: Resolution, numImages?: number): number | null;
432
435
  /** Estimate cost for video generation */
433
436
  export declare function estimateVideoCost(durationSeconds?: number, generateAudio?: boolean): number;
434
437
  //#endregion
@@ -485,9 +488,9 @@ export declare const VIDEO_IMAGE_TO_VIDEO_TOP_15: LeaderboardSnapshot;
485
488
  //#endregion
486
489
  //#region src/models.d.ts
487
490
  export declare const MODELS: Record<string, ModelConfig>;
488
- export declare const GENERATION_MODELS: readonly ["gpt2", "gpt", "banana2", "banana", "gemini", "gemini3", "seedream4", "seedream45", "seedream5", "seedream5-lite", "flux2-max", "flux2-pro", "flux2-flex", "flux2-dev", "flux2-turbo", "flux", "flux-fast", "recraft", "recraft4", "ideogram", "ideogram4", "grok-image", "qwen", "qwen3"];
491
+ export declare const GENERATION_MODELS: readonly ["flare", "sunburst", "gpt2", "gpt", "banana2", "banana", "gemini", "gemini3", "seedream4", "seedream45", "seedream5", "seedream5-lite", "flux2-max", "flux2-pro", "flux2-flex", "flux2-dev", "flux2-turbo", "flux", "flux-fast", "recraft", "recraft4", "ideogram", "ideogram4", "grok-image", "qwen", "qwen3"];
489
492
  /** Models whose fal endpoints support image editing (vary/edit flows). */
490
- export declare const EDIT_CAPABLE_MODELS: ("banana" | "banana2" | "flux" | "flux-fast" | "flux2-dev" | "flux2-flex" | "flux2-max" | "flux2-pro" | "flux2-turbo" | "gemini" | "gemini3" | "gpt" | "gpt2" | "grok-image" | "ideogram" | "ideogram4" | "qwen" | "qwen3" | "recraft" | "recraft4" | "seedream4" | "seedream45" | "seedream5" | "seedream5-lite")[];
493
+ export declare const EDIT_CAPABLE_MODELS: ("banana" | "banana2" | "flare" | "flux" | "flux-fast" | "flux2-dev" | "flux2-flex" | "flux2-max" | "flux2-pro" | "flux2-turbo" | "gemini" | "gemini3" | "gpt" | "gpt2" | "grok-image" | "ideogram" | "ideogram4" | "qwen" | "qwen3" | "recraft" | "recraft4" | "seedream4" | "seedream45" | "seedream5" | "seedream5-lite" | "sunburst")[];
491
494
  export declare const UTILITY_MODELS: readonly ["clarity", "crystal", "rmbg", "bria"];
492
495
  export declare const VIDEO_MODELS: readonly ["kling"];
493
496
  type GenerationModelName = (typeof GENERATION_MODELS)[number];
@@ -608,7 +611,7 @@ export declare class FalClient {
608
611
  */
609
612
  deletePayloads(requestId: string): Promise<Result$1<void, MotifError>>;
610
613
  /** Estimate cost for a generation (no API call). */
611
- estimateCost(model: string, resolution?: Resolution, numImages?: number): number;
614
+ estimateCost(model: string, resolution?: Resolution, numImages?: number): number | null;
612
615
  /** Build the fal.ai request body without sending it. */
613
616
  buildRequestBody(options: GenerateOptions): {
614
617
  endpoint: string;
@@ -617,7 +620,7 @@ export declare class FalClient {
617
620
  /** Model registry. */
618
621
  get models(): Record<string, ModelConfig>;
619
622
  /** Generation model keys. */
620
- get generationModels(): readonly ["gpt2", "gpt", "banana2", "banana", "gemini", "gemini3", "seedream4", "seedream45", "seedream5", "seedream5-lite", "flux2-max", "flux2-pro", "flux2-flex", "flux2-dev", "flux2-turbo", "flux", "flux-fast", "recraft", "recraft4", "ideogram", "ideogram4", "grok-image", "qwen", "qwen3"];
623
+ get generationModels(): readonly ["flare", "sunburst", "gpt2", "gpt", "banana2", "banana", "gemini", "gemini3", "seedream4", "seedream45", "seedream5", "seedream5-lite", "flux2-max", "flux2-pro", "flux2-flex", "flux2-dev", "flux2-turbo", "flux", "flux-fast", "recraft", "recraft4", "ideogram", "ideogram4", "grok-image", "qwen", "qwen3"];
621
624
  /** Utility model keys. */
622
625
  get utilityModels(): readonly ["clarity", "crystal", "rmbg", "bria"];
623
626
  /** Registered fal utility/tool endpoints. */
package/dist/index.js CHANGED
@@ -111,6 +111,64 @@ const AA_IMAGE_SOURCES = ["https://artificialanalysis.ai/image/leaderboard/text-
111
111
  const FAL_PRICING_CHECKED_AT = "2026-05-12";
112
112
  const FAL_PRICING_CHECKED_JUL_2026 = "2026-07-11";
113
113
  const MODELS = {
114
+ flare: {
115
+ editEndpoint: "openai/gpt-image-2.5/flare/edit",
116
+ endpoint: "openai/gpt-image-2.5/flare/text-to-image",
117
+ maxReferenceImages: 16,
118
+ name: "GPT Image 2.5 Flare",
119
+ pricing: "Token-based: text $5/M input, $1.25/M cached, $10/M output; image $8/M input, $2/M cached, $30/M output",
120
+ pricePerImageUsd: null,
121
+ sizeMode: "image_size_enum",
122
+ supportsAspect: true,
123
+ supportsBackground: true,
124
+ supportsEdit: true,
125
+ supportsMaskImage: true,
126
+ maskImageField: "mask_url",
127
+ supportsNumImages: true,
128
+ supportsOutputFormat: true,
129
+ supportsQuality: true,
130
+ supportedQualities: [
131
+ "auto",
132
+ "low",
133
+ "medium",
134
+ "high",
135
+ "xhigh",
136
+ "max"
137
+ ],
138
+ supportsResolution: false,
139
+ supportsSyncMode: true,
140
+ type: "generation",
141
+ useQueue: true
142
+ },
143
+ sunburst: {
144
+ editEndpoint: "openai/gpt-image-2.5/sunburst/edit",
145
+ endpoint: "openai/gpt-image-2.5/sunburst/text-to-image",
146
+ maxReferenceImages: 16,
147
+ name: "GPT Image 2.5 Sunburst",
148
+ pricing: "Token-based: text $5/M input, $1.25/M cached, $10/M output; image $8/M input, $2/M cached, $30/M output",
149
+ pricePerImageUsd: null,
150
+ sizeMode: "image_size_enum",
151
+ supportsAspect: true,
152
+ supportsBackground: true,
153
+ supportsEdit: true,
154
+ supportsMaskImage: true,
155
+ maskImageField: "mask_url",
156
+ supportsNumImages: true,
157
+ supportsOutputFormat: true,
158
+ supportsQuality: true,
159
+ supportedQualities: [
160
+ "auto",
161
+ "low",
162
+ "medium",
163
+ "high",
164
+ "xhigh",
165
+ "max"
166
+ ],
167
+ supportsResolution: false,
168
+ supportsSyncMode: true,
169
+ type: "generation",
170
+ useQueue: true
171
+ },
114
172
  gpt2: {
115
173
  benchmark: {
116
174
  artificialAnalysis: {
@@ -1205,6 +1263,8 @@ const MODELS = {
1205
1263
  }
1206
1264
  };
1207
1265
  const GENERATION_MODELS = [
1266
+ "flare",
1267
+ "sunburst",
1208
1268
  "gpt2",
1209
1269
  "gpt",
1210
1270
  "banana2",
@@ -1287,6 +1347,7 @@ const IDEOGRAM_STYLES = [
1287
1347
  /** Estimate cost based on model and settings */
1288
1348
  function estimateCost(model, resolution, numImages = 1) {
1289
1349
  const configuredPrice = MODELS[model]?.pricePerImageUsd;
1350
+ if (configuredPrice === null) return null;
1290
1351
  if (configuredPrice !== void 0) {
1291
1352
  if ((model === "banana" || model === "gemini3") && resolution === "4K") return configuredPrice * 2 * numImages;
1292
1353
  if (model === "seedream5" && (resolution === "2K" || resolution === "4K")) return configuredPrice * 2 * numImages;
@@ -1512,6 +1573,15 @@ function validateGenerateOptions(options, config, sizeMode, hasEditImages) {
1512
1573
  if (options.seed !== void 0 && config.supportsSeed !== true) unsupported(config, "seed");
1513
1574
  if (options.outputFormat !== void 0 && config.supportsOutputFormat !== true) unsupported(config, "outputFormat");
1514
1575
  if (options.quality !== void 0 && config.supportsQuality !== true) unsupported(config, "quality");
1576
+ if (options.quality !== void 0) {
1577
+ const qualities = config.supportedQualities ?? [
1578
+ "auto",
1579
+ "low",
1580
+ "medium",
1581
+ "high"
1582
+ ];
1583
+ if (!qualities.includes(options.quality)) throw new Error(`${config.name} quality must be one of ${qualities.join(", ")}`);
1584
+ }
1515
1585
  if (options.negativePrompt !== void 0 && config.supportsNegativePrompt !== true) unsupported(config, "negativePrompt");
1516
1586
  if (options.style !== void 0 && config.supportsStyle !== true) unsupported(config, "style");
1517
1587
  if (options.syncMode !== void 0 && config.supportsSyncMode !== true) unsupported(config, "syncMode");
@@ -1630,7 +1700,7 @@ function buildGenerateBody(options) {
1630
1700
  body.image_urls = editImages;
1631
1701
  if (inputFidelity) body.input_fidelity = inputFidelity;
1632
1702
  }
1633
- if (maskImageUrl !== void 0 && maskImageUrl !== "" && config.supportsMaskImage === true) body.mask_image_url = maskImageUrl;
1703
+ if (maskImageUrl !== void 0 && maskImageUrl !== "" && config.supportsMaskImage === true) body[config.maskImageField ?? "mask_image_url"] = maskImageUrl;
1634
1704
  return {
1635
1705
  body,
1636
1706
  endpoint
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@howells/motif-sdk",
3
- "version": "1.3.1",
3
+ "version": "2.0.0",
4
4
  "description": "Public Node SDK for Motif fal.ai generation, editing, utility tools, and model metadata.",
5
5
  "keywords": [
6
6
  "fal",