@howells/motif-sdk 5.3.0 → 6.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
@@ -44,7 +44,7 @@ The Task client curates 24 generation Models, with separate rankings for generat
44
44
 
45
45
  These are the first choices when the request's capabilities and available keys permit them. Explicit Models, Looks and pins take precedence. Selected specialists remain for typography, transparency, control and inexpensive drafts. Archived predecessors remain available through explicit Models, pins and existing Looks, including `flux2-dev` for streaming.
46
46
 
47
- Nano Banana 2.1 (`banana21`) supports up to 14 References and 1K/2K/4K output, with no 0.5K output. Its balanced placement uses preliminary Arena evidence. Sunburst and Flare default to the benchmarked `max` quality on fal; `params: { quality: "high" }` with an explicit `model` overrides it. The direct provider image layer described below has its own adapter capabilities and chooses no Model.
47
+ Nano Banana 2.1 (`banana21`) supports up to 14 References and 1K/2K/4K output, with no 0.5K output. Its balanced placement uses preliminary Arena evidence. Sunburst and Flare run GPT Image 2.6 on fal and default to `quality: "max"`; `params: { quality: "high" }` with an explicit `model` overrides it. Both also return an animated GIF (`outputFormat: "gif"`, with `params.frame_count` and `params.frame_duration_ms`) or, from text, an SVG (`outputFormat: "svg"`), and take `markup`: a transparent PNG of arrows, circles and notes the size of the image being edited, which guides a vary or an edit with References. Asking for a format or markup limits the choice to Models that support it. The direct provider image layer described below has its own adapter capabilities and chooses no Model.
48
48
 
49
49
  Token-billed Task plans, including Sunburst, Flare, Nano Banana 2.1, Banana Lite and MAI 2.5, report `cost: { usd: null, basis: "unknown" }`; a published representative price is not an exact total. The [curation audit](https://github.com/howells/motif/blob/main/docs/research/image-model-curation-2026-10-07.md) records the current rankings, specialist roles and provider coverage gaps.
50
50
 
@@ -73,14 +73,14 @@ if (plan.isOk()) {
73
73
 
74
74
  ## Stream a Task
75
75
 
76
- `stream(task, input, options?)` opens a direct inference stream on the resolved route. Streaming is explicitly supported for GPT Image 2, GPT Image 1.5 and FLUX.2 Dev, including their edit routes. Other routes return `STREAMING_UNSUPPORTED` before any provider request; Motif never substitutes another model or retries a stream.
76
+ `stream(task, input, options?)` opens a direct inference stream on the resolved route. Streaming is explicitly supported for GPT Image 1.5 and FLUX.2 Dev, including their edit routes. Other routes return `STREAMING_UNSUPPORTED` before any provider request; Motif never substitutes another model or retries a stream.
77
77
 
78
78
  ```ts
79
79
  const controller = new AbortController();
80
80
  const opened = await motif.stream(
81
81
  "generate",
82
82
  {
83
- model: "gpt2",
83
+ model: "gpt",
84
84
  prompt: "editorial product photo",
85
85
  references: ["https://example.com/reference.png"],
86
86
  },
@@ -110,7 +110,7 @@ For a local checkout, `node packages/motif-sdk/examples/stream.mjs --dry-run` pr
110
110
  - `ASPECT_RATIOS`, `RESOLUTIONS`, `FORMAT_PRESETS` - shared sizing metadata.
111
111
  - `LOOKS`, `CREATIVE_TAXONOMY`, `enrichPrompt`, `validateCreativeDirection` - house looks and moods.
112
112
  - `formatCost`, `sumCosts` - cost formatting and totals.
113
- - `getFalKeyFromEnv`, `getOpenAiKeyFromEnv` - `@howells/envy` backed key parsing.
113
+ - `getFalKeyFromEnv` - `@howells/envy` backed key parsing.
114
114
  - Re-exported `neverthrow` helpers: `ok`, `err`, `Result`, `ResultAsync`.
115
115
 
116
116
  ## Common Types
@@ -161,28 +161,28 @@ Four providers are implemented, each reading its own API key from the environmen
161
161
  | Provider | Env var | Notes |
162
162
  | --- | --- | --- |
163
163
  | `openrouter` | `OPENROUTER_API_KEY` | Default provider; Gemini gen + edit through OpenRouter (`google/*`), no masks |
164
- | `openai` | `OPENAI_API_KEY` | GPT Image 2.5 Flare and Sunburst |
164
+ | `openai` | `OPENAI_API_KEY` | GPT Image 2.6 Flare and Sunburst |
165
165
  | `replicate` | `REPLICATE_API_TOKEN` | flux-1.1-pro-ultra |
166
166
  | `fal` | `FAL_KEY` | fal-hosted adapter |
167
167
 
168
168
  `generate()` and `edit()` take a provider model id; Task resolution chooses which model to pass, not the image layer. Every result carries a normalized per-call `cost: { usd, source }`.
169
169
 
170
- Both `gpt-image-2.5-flare` and `gpt-image-2.5-sunburst` support generation and multi-image editing:
170
+ Both `gpt-image-2.6-flare` and `gpt-image-2.6-sunburst` support generation and multi-image editing:
171
171
 
172
172
  ```ts
173
173
  const image = createMotifImage({ defaultProvider: "openai" });
174
174
  const generated = await image.generate({
175
175
  prompt: "A ceramic vase in soft window light",
176
- model: "gpt-image-2.5-flare",
176
+ model: "gpt-image-2.6-flare",
177
177
  });
178
178
  const refined = await image.edit({
179
179
  images: [referenceBytes],
180
180
  instruction: "Change only the vase glaze to deep green",
181
- model: "gpt-image-2.5-sunburst",
181
+ model: "gpt-image-2.6-sunburst",
182
182
  });
183
183
  ```
184
184
 
185
- 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 Task client and the CLI reach these models on fal as `flare` and `sunburst`. Fal supports `xhigh` and `max`, up to 16 edit references, masks and transparent backgrounds. Fal generation estimates are `null` (metered).
185
+ 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.6-flare) and [Sunburst](https://developers.openai.com/api/docs/models/gpt-image-2.6-sunburst) model pages. Set quality (`auto`, `low`, `medium`, `high`, `xhigh` or `max`) and `background: "transparent"` through `providerOptions.openai`. The Task client and the CLI run the same models on fal as `flare` and `sunburst`, with up to 16 edit references, masks and transparent backgrounds. Fal generation estimates are `null` (metered).
186
186
 
187
187
  ### Best-of-N with an injectable judge
188
188
 
@@ -218,9 +218,3 @@ If some candidates fail, the judge sees only the survivors; if all `n` fail, `be
218
218
  pnpm --filter @howells/motif-sdk test
219
219
  pnpm --filter @howells/motif-sdk typecheck
220
220
  ```
221
-
222
- Live fal canaries are opt-in:
223
-
224
- ```bash
225
- RUN_FAL_CANARY=1 pnpm --filter @howells/motif-sdk test -- tests/fal-canary.test.ts
226
- ```
package/dist/image.js CHANGED
@@ -73,7 +73,7 @@ const MODELS = {
73
73
  rank: 2
74
74
  }
75
75
  },
76
- useCase: "Frontier generation and editing, evaluated at max quality"
76
+ useCase: "Frontier generation and editing at max reasoning effort; scored as GPT Image 2.5 until 2.6 is benchmarked"
77
77
  },
78
78
  defaultQuality: "max",
79
79
  customImageSize: {
@@ -83,11 +83,11 @@ const MODELS = {
83
83
  minPixels: 655360,
84
84
  multipleOf: 16
85
85
  },
86
- editEndpoint: "openai/gpt-image-2.5/flare/edit",
87
- endpoint: "openai/gpt-image-2.5/flare/text-to-image",
86
+ editEndpoint: "openai/gpt-image-2.6/flare/edit",
87
+ endpoint: "openai/gpt-image-2.6/flare/text-to-image",
88
88
  maxReferenceImages: 16,
89
- name: "GPT Image 2.5 Flare",
90
- pricing: "Token-based: text $5/M input, $1.25/M cached, $10/M output; image $8/M input, $2/M cached, $30/M output",
89
+ name: "GPT Image 2.6 Flare",
90
+ pricing: "Token-based: image $8/M input, $2/M cached, $30/M output; text $5/M input, $1.25/M cached",
91
91
  pricePerImageUsd: null,
92
92
  sizeMode: "image_size_enum",
93
93
  supportsAspect: true,
@@ -95,8 +95,23 @@ const MODELS = {
95
95
  supportsEdit: true,
96
96
  supportsMaskImage: true,
97
97
  maskImageField: "mask_url",
98
+ supportsMarkupImage: true,
99
+ autoEditSize: true,
98
100
  supportsNumImages: true,
99
101
  supportsOutputFormat: true,
102
+ supportedOutputFormats: [
103
+ "jpeg",
104
+ "png",
105
+ "webp",
106
+ "gif",
107
+ "svg"
108
+ ],
109
+ supportedEditOutputFormats: [
110
+ "jpeg",
111
+ "png",
112
+ "webp",
113
+ "gif"
114
+ ],
100
115
  supportsQuality: true,
101
116
  supportedQualities: [
102
117
  "auto",
@@ -125,7 +140,7 @@ const MODELS = {
125
140
  rank: 1
126
141
  }
127
142
  },
128
- useCase: "Frontier generation and editing, evaluated at max quality"
143
+ useCase: "Frontier generation and editing at max reasoning effort; scored as GPT Image 2.5 until 2.6 is benchmarked"
129
144
  },
130
145
  defaultQuality: "max",
131
146
  customImageSize: {
@@ -135,11 +150,11 @@ const MODELS = {
135
150
  minPixels: 655360,
136
151
  multipleOf: 16
137
152
  },
138
- editEndpoint: "openai/gpt-image-2.5/sunburst/edit",
139
- endpoint: "openai/gpt-image-2.5/sunburst/text-to-image",
153
+ editEndpoint: "openai/gpt-image-2.6/sunburst/edit",
154
+ endpoint: "openai/gpt-image-2.6/sunburst/text-to-image",
140
155
  maxReferenceImages: 16,
141
- name: "GPT Image 2.5 Sunburst",
142
- pricing: "Token-based: text $5/M input, $1.25/M cached, $10/M output; image $8/M input, $2/M cached, $30/M output",
156
+ name: "GPT Image 2.6 Sunburst",
157
+ pricing: "Token-based: image $8/M input, $2/M cached, $30/M output; text $5/M input, $1.25/M cached",
143
158
  pricePerImageUsd: null,
144
159
  sizeMode: "image_size_enum",
145
160
  supportsAspect: true,
@@ -147,8 +162,23 @@ const MODELS = {
147
162
  supportsEdit: true,
148
163
  supportsMaskImage: true,
149
164
  maskImageField: "mask_url",
165
+ supportsMarkupImage: true,
166
+ autoEditSize: true,
150
167
  supportsNumImages: true,
151
168
  supportsOutputFormat: true,
169
+ supportedOutputFormats: [
170
+ "jpeg",
171
+ "png",
172
+ "webp",
173
+ "gif",
174
+ "svg"
175
+ ],
176
+ supportedEditOutputFormats: [
177
+ "jpeg",
178
+ "png",
179
+ "webp",
180
+ "gif"
181
+ ],
152
182
  supportsQuality: true,
153
183
  supportedQualities: [
154
184
  "auto",
@@ -163,77 +193,6 @@ const MODELS = {
163
193
  type: "generation",
164
194
  useQueue: true
165
195
  },
166
- gpt2: {
167
- benchmark: {
168
- artificialAnalysis: {
169
- editing: {
170
- elo: 1125,
171
- rank: 5
172
- },
173
- snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
174
- sourceUrls: AA_IMAGE_SOURCES,
175
- textToImage: {
176
- elo: 1172,
177
- rank: 3
178
- }
179
- },
180
- speed: {
181
- medianSeconds: 200.7,
182
- p95Seconds: 232.6,
183
- source: "artificial-analysis-models"
184
- },
185
- tiers: {
186
- price: "ultra",
187
- quality: "frontier",
188
- speed: "very_slow"
189
- },
190
- useCase: "Established frontier generation and transparent PNGs"
191
- },
192
- customImageSize: {
193
- maxEdge: 3840,
194
- maxPixels: 8294400,
195
- maxRatio: 3,
196
- minPixels: 655360,
197
- multipleOf: 16
198
- },
199
- editEndpoint: "openai/gpt-image-2/edit",
200
- endpoint: "openai/gpt-image-2",
201
- falPricing: {
202
- checkedAt: FAL_PRICING_CHECKED_AT,
203
- currency: "USD",
204
- endpointId: "openai/gpt-image-2",
205
- estimatedCostPerImageUsd: .211,
206
- source: "fal-pricing-api",
207
- unit: "units",
208
- unitPrice: 1
209
- },
210
- maxReferenceImages: 16,
211
- name: "GPT Image 2",
212
- pricePerImageUsd: .211,
213
- pricing: "$0.211",
214
- sizeMode: "image_size_enum",
215
- supportsAspect: true,
216
- streaming: {
217
- generation: true,
218
- edit: true
219
- },
220
- supportsEdit: true,
221
- supportsMaskImage: true,
222
- maskImageField: "mask_url",
223
- supportsNumImages: true,
224
- supportsOutputFormat: true,
225
- supportsQuality: true,
226
- supportsResolution: false,
227
- supportsSyncMode: true,
228
- transparencyRoute: {
229
- apiKeyEnv: "OPENAI_API_KEY",
230
- model: "gpt-image-2",
231
- provider: "openai",
232
- supportsEdit: true
233
- },
234
- type: "generation",
235
- useQueue: true
236
- },
237
196
  gpt: {
238
197
  benchmark: {
239
198
  artificialAnalysis: {
@@ -1814,7 +1773,6 @@ const MODELS = {
1814
1773
  [
1815
1774
  "sunburst",
1816
1775
  "flare",
1817
- "gpt2",
1818
1776
  "gpt",
1819
1777
  "banana21",
1820
1778
  "banana2",
@@ -1854,6 +1812,7 @@ const OPTION_CAPABILITIES = {
1854
1812
  imageSize: (config) => config.sizeMode === "gpt_size" || config.sizeMode === "image_size_enum",
1855
1813
  inputFidelity: (config) => config.sizeMode === "gpt_size",
1856
1814
  limitGenerations: (config) => config.supportsLimitGenerations === true,
1815
+ markupImageUrl: (config) => config.supportsMarkupImage === true,
1857
1816
  maskImageUrl: (config) => config.supportsMaskImage === true,
1858
1817
  negativePrompt: (config) => config.supportsNegativePrompt === true,
1859
1818
  numImages: (config) => config.supportsNumImages,
@@ -1956,7 +1915,7 @@ Object.keys(OPTION_CAPABILITIES).filter(isModelOption);
1956
1915
  experimental: true,
1957
1916
  id: "illustration",
1958
1917
  label: "Editorial illustration",
1959
- model: "gpt2"
1918
+ model: "flare"
1960
1919
  }
1961
1920
  ],
1962
1921
  mood: [
@@ -3546,7 +3505,6 @@ const FAL_IMAGE_PRICE_USD = {
3546
3505
  "fal-ai/flux-2": MODELS["flux2-dev"]?.pricePerImageUsd ?? .012,
3547
3506
  "fal-ai/flux-2/turbo": MODELS["flux2-turbo"]?.pricePerImageUsd ?? .008,
3548
3507
  [FAL_GPT_IMAGE_MODEL]: MODELS.gpt?.pricePerImageUsd ?? .133,
3549
- "openai/gpt-image-2": MODELS.gpt2?.pricePerImageUsd ?? .211,
3550
3508
  "fal-ai/nano-banana-2": MODELS.banana2?.pricePerImageUsd ?? .08,
3551
3509
  "fal-ai/nano-banana-pro": MODELS.banana?.pricePerImageUsd ?? .15,
3552
3510
  "fal-ai/gemini-25-flash-image": MODELS.gemini?.pricePerImageUsd ?? .0398,
@@ -3621,10 +3579,10 @@ const OPENAI_API_KEY_ENV = "OPENAI_API_KEY";
3621
3579
  * Sanity anchor: the Phase 0 benchmark measured gpt-image direct at $0.042
3622
3580
  * (vs $0.133 via fal — full data and methodology on Linear MOT-23).
3623
3581
  *
3624
- * GPT Image 2.5 is token-priced, with no published per-image estimate. Leave
3582
+ * GPT Image 2.6 is token-priced, with no published per-image estimate. Leave
3625
3583
  * these models absent so cost remains unknown unless supplied by the provider.
3626
- * https://developers.openai.com/api/docs/models/gpt-image-2.5-flare
3627
- * https://developers.openai.com/api/docs/models/gpt-image-2.5-sunburst
3584
+ * https://developers.openai.com/api/docs/models/gpt-image-2.6-flare
3585
+ * https://developers.openai.com/api/docs/models/gpt-image-2.6-sunburst
3628
3586
  */
3629
3587
  const OPENAI_IMAGE_PRICE_USD = { "gpt-image-1": .042 };
3630
3588
  /**