@howells/motif-sdk 0.6.0 → 0.8.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/image.d.ts +316 -0
- package/dist/image.js +1655 -0
- package/dist/index.cjs +3 -3
- package/package.json +12 -3
package/dist/image.d.ts
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { ImageModel, generateImage } from 'ai';
|
|
2
|
+
import { Result } from 'neverthrow';
|
|
3
|
+
|
|
4
|
+
declare class MotifError extends Error {
|
|
5
|
+
readonly status: number;
|
|
6
|
+
readonly code?: string;
|
|
7
|
+
/** fal's request-correlation id (from the `x-fal-request-id` header or the
|
|
8
|
+
* error body). Ties a failure back to fal's dashboard/support. */
|
|
9
|
+
readonly requestId?: string;
|
|
10
|
+
constructor(message: string, status: number, code?: string, requestId?: string);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Public types for the provider-agnostic image generation + editing layer
|
|
15
|
+
* (`@howells/motif-sdk/image`).
|
|
16
|
+
*
|
|
17
|
+
* This layer is additive: it sits alongside the fal-specific `MotifServer`
|
|
18
|
+
* surface and reuses the SDK's Result convention (`Result<T, MotifError>` — no
|
|
19
|
+
* thrown exceptions). See docs/design/provider-agnostic-image-layer.md.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Quality/latency tier. Resolves through a provider-aware tier→model map when no
|
|
24
|
+
* explicit `model` id is given. `balanced` is the default when a tier is omitted.
|
|
25
|
+
*/
|
|
26
|
+
type ImageTier = "fast" | "balanced" | "quality" | "hero";
|
|
27
|
+
/**
|
|
28
|
+
* Image provider id. All four Phase 1b adapters are implemented
|
|
29
|
+
* (`google`, `openai`, `replicate`, `fal`); the type keeps an open union tail so
|
|
30
|
+
* further adapters can slot in without a breaking type change.
|
|
31
|
+
*/
|
|
32
|
+
type ImageProviderId = "google" | "openai" | "replicate" | "fal" | (string & Record<never, never>);
|
|
33
|
+
/** Source that produced a normalized per-call cost. */
|
|
34
|
+
type ImageCostSource = "provider-metadata" | "table" | "unknown";
|
|
35
|
+
/** Normalized per-call spend attached to every result. */
|
|
36
|
+
interface ImageCost {
|
|
37
|
+
/** Total USD for the call (all images), best-effort. */
|
|
38
|
+
usd: number;
|
|
39
|
+
/** Where the figure came from: the provider's metadata, the static table, or unknown. */
|
|
40
|
+
source: ImageCostSource;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Client configuration. Every provider key is optional and falls back to that
|
|
44
|
+
* provider's environment variable (see each adapter's `apiKeyEnv`). The config
|
|
45
|
+
* field name mirrors each SDK's own option name — notably Replicate calls its
|
|
46
|
+
* credential `apiToken`, not `apiKey`.
|
|
47
|
+
*/
|
|
48
|
+
interface MotifImageConfig {
|
|
49
|
+
/** Provider used when a call does not specify one. Defaults to `google`. */
|
|
50
|
+
defaultProvider?: ImageProviderId;
|
|
51
|
+
/** Google provider overrides. `apiKey` falls back to `GOOGLE_GENERATIVE_AI_API_KEY`. */
|
|
52
|
+
google?: {
|
|
53
|
+
apiKey?: string;
|
|
54
|
+
};
|
|
55
|
+
/** OpenAI provider overrides. `apiKey` falls back to `OPENAI_API_KEY`. */
|
|
56
|
+
openai?: {
|
|
57
|
+
apiKey?: string;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Replicate provider overrides. Replicate's SDK names the credential
|
|
61
|
+
* `apiToken` (not `apiKey`); it falls back to `REPLICATE_API_TOKEN`.
|
|
62
|
+
*/
|
|
63
|
+
replicate?: {
|
|
64
|
+
apiToken?: string;
|
|
65
|
+
};
|
|
66
|
+
/** fal provider overrides. `apiKey` falls back to `FAL_KEY`. */
|
|
67
|
+
fal?: {
|
|
68
|
+
apiKey?: string;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** Options for a text→image generation. */
|
|
72
|
+
interface GenerateImageOptions {
|
|
73
|
+
/** The text prompt. */
|
|
74
|
+
prompt: string;
|
|
75
|
+
/** Quality/latency tier. Ignored when `model` is set. */
|
|
76
|
+
tier?: ImageTier;
|
|
77
|
+
/** Explicit provider model id. Overrides `tier`. */
|
|
78
|
+
model?: string;
|
|
79
|
+
/** Provider override for this call. */
|
|
80
|
+
provider?: ImageProviderId;
|
|
81
|
+
/** Aspect ratio, e.g. `"1:1"`. */
|
|
82
|
+
aspectRatio?: `${number}:${number}`;
|
|
83
|
+
/** Explicit pixel size, e.g. `"1024x1024"`. */
|
|
84
|
+
size?: `${number}x${number}`;
|
|
85
|
+
/** Number of images to generate. */
|
|
86
|
+
n?: number;
|
|
87
|
+
/**
|
|
88
|
+
* Provider-specific options, passed straight through to the underlying model
|
|
89
|
+
* as body parameters. Outer key = provider name, inner key = option name.
|
|
90
|
+
*/
|
|
91
|
+
providerOptions?: Record<string, Record<string, unknown>>;
|
|
92
|
+
}
|
|
93
|
+
/** Options for a multi-image edit (images in → image out), with an optional mask. */
|
|
94
|
+
interface EditImageOptions {
|
|
95
|
+
/** Input images. Each is raw bytes, a base64 string, or a data URL. */
|
|
96
|
+
images: (Uint8Array | string)[];
|
|
97
|
+
/** Natural-language edit instruction. */
|
|
98
|
+
instruction: string;
|
|
99
|
+
/** Optional mask (bytes, base64, or data URL) constraining the edited region. */
|
|
100
|
+
mask?: Uint8Array | string;
|
|
101
|
+
/** Quality/latency tier. Ignored when `model` is set. */
|
|
102
|
+
tier?: ImageTier;
|
|
103
|
+
/** Explicit provider model id. Overrides `tier`. */
|
|
104
|
+
model?: string;
|
|
105
|
+
/** Provider override for this call. */
|
|
106
|
+
provider?: ImageProviderId;
|
|
107
|
+
/** Number of images to generate. */
|
|
108
|
+
n?: number;
|
|
109
|
+
/** Provider-specific options (see {@link GenerateImageOptions.providerOptions}). */
|
|
110
|
+
providerOptions?: Record<string, Record<string, unknown>>;
|
|
111
|
+
}
|
|
112
|
+
/** A single generated image, mirroring the AI SDK's `GeneratedFile`. */
|
|
113
|
+
interface MotifImageFile {
|
|
114
|
+
uint8Array: Uint8Array;
|
|
115
|
+
base64: string;
|
|
116
|
+
mediaType: string;
|
|
117
|
+
}
|
|
118
|
+
/** Normalized result of a generate/edit call. */
|
|
119
|
+
interface MotifImageResult {
|
|
120
|
+
/** Generated images. */
|
|
121
|
+
images: MotifImageFile[];
|
|
122
|
+
/** Normalized per-call cost + provenance. */
|
|
123
|
+
cost: ImageCost;
|
|
124
|
+
/** Resolved provider. */
|
|
125
|
+
provider: ImageProviderId;
|
|
126
|
+
/** Resolved model id. */
|
|
127
|
+
model: string;
|
|
128
|
+
/** Provider correlation id, where the provider surfaces one. */
|
|
129
|
+
requestId?: string;
|
|
130
|
+
}
|
|
131
|
+
/** The provider-agnostic image client. Every method returns a Result — no throws. */
|
|
132
|
+
interface MotifImageClient {
|
|
133
|
+
/** Text→image generation. */
|
|
134
|
+
generate: (opts: GenerateImageOptions) => Promise<Result<MotifImageResult, MotifError>>;
|
|
135
|
+
/** Multi-image edit (with optional mask). */
|
|
136
|
+
edit: (opts: EditImageOptions) => Promise<Result<MotifImageResult, MotifError>>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Provider registry for the image layer.
|
|
141
|
+
*
|
|
142
|
+
* Each provider (google, openai, replicate, fal) contributes exactly one
|
|
143
|
+
* {@link ImageProviderAdapter}. The dispatch functions in `index.ts` and the
|
|
144
|
+
* cost lookup in `cost.ts` read the registry by id, so adding a provider is a
|
|
145
|
+
* single registry entry — not new branches spread across generate/edit/cost.
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* A single image provider. A thin wrapper over the provider's `@ai-sdk/*` image
|
|
150
|
+
* model, plus the metadata the layer needs to route by tier, resolve keys, and
|
|
151
|
+
* meter spend.
|
|
152
|
+
*/
|
|
153
|
+
interface ImageProviderAdapter {
|
|
154
|
+
/** Provider id, matching the key it is registered under in {@link PROVIDERS}. */
|
|
155
|
+
readonly id: ImageProviderId;
|
|
156
|
+
/** Tier → model id map, used when a call does not pass an explicit `model`. */
|
|
157
|
+
readonly tierModels: Readonly<Record<ImageTier, string>>;
|
|
158
|
+
/** Env var read for the API key when no key is supplied in config. */
|
|
159
|
+
readonly apiKeyEnv: string;
|
|
160
|
+
/**
|
|
161
|
+
* Build the AI SDK `ImageModel` for a model id. Prefers the passed `apiKey`,
|
|
162
|
+
* else the adapter's `apiKeyEnv`. Throws `MotifError` when neither is present
|
|
163
|
+
* (callers translate this into a `Result.err`). Building a model performs no
|
|
164
|
+
* network I/O.
|
|
165
|
+
*/
|
|
166
|
+
readonly resolveModel: (modelId: string, apiKey?: string) => ImageModel;
|
|
167
|
+
/** Static per-model USD/image table (best-effort; cited per adapter). */
|
|
168
|
+
readonly priceUsdByModel: Readonly<Record<string, number>>;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* The provider registry. Every id in {@link ImageProviderId}'s closed part maps
|
|
172
|
+
* to its adapter; the open union tail means a lookup can still miss, so access
|
|
173
|
+
* goes through {@link getProviderAdapter}.
|
|
174
|
+
*/
|
|
175
|
+
declare const PROVIDERS: Record<ImageProviderId, ImageProviderAdapter>;
|
|
176
|
+
/**
|
|
177
|
+
* Look up an adapter by provider id, throwing a `MotifError` for an unknown
|
|
178
|
+
* provider (callers catch this into a `Result.err`).
|
|
179
|
+
*/
|
|
180
|
+
declare function getProviderAdapter(provider: ImageProviderId): ImageProviderAdapter;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Google (Gemini) provider adapter.
|
|
184
|
+
*
|
|
185
|
+
* Builds a Vercel AI SDK `ImageModel` from `@ai-sdk/google`. Building a model
|
|
186
|
+
* performs no network I/O — the request only happens when `generateImage`
|
|
187
|
+
* invokes `model.doGenerate`. Gemini supports both text→image generation and
|
|
188
|
+
* multi-image-in → image-out editing (with an optional mask), which is the core
|
|
189
|
+
* operation this layer normalizes.
|
|
190
|
+
*/
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Tier → Gemini image model id.
|
|
194
|
+
*
|
|
195
|
+
* Seeded from Material Desk's `RENDER_IMAGE_MODEL_BY_QUALITY` (the driving
|
|
196
|
+
* consumer, see the design doc). `gemini-2.5-flash-image` is the proven-reachable
|
|
197
|
+
* floor; the preview ids may require allowlist/tier access.
|
|
198
|
+
*/
|
|
199
|
+
declare const GOOGLE_TIER_MODELS: Readonly<Record<ImageTier, string>>;
|
|
200
|
+
/** Env var read for the Google API key when `apiKey` is not supplied in config. */
|
|
201
|
+
declare const GOOGLE_API_KEY_ENV = "GOOGLE_GENERATIVE_AI_API_KEY";
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* OpenAI (gpt-image) provider adapter.
|
|
205
|
+
*
|
|
206
|
+
* Builds a Vercel AI SDK `ImageModel` from `@ai-sdk/openai`. Building a model
|
|
207
|
+
* performs no network I/O — the request only happens when `generateImage`
|
|
208
|
+
* invokes `model.doGenerate`.
|
|
209
|
+
*/
|
|
210
|
+
|
|
211
|
+
/** Tier → OpenAI image model id (all tiers → the single gpt-image model). */
|
|
212
|
+
declare const OPENAI_TIER_MODELS: Readonly<Record<ImageTier, string>>;
|
|
213
|
+
/** Env var read for the OpenAI API key when `apiKey` is not supplied in config. */
|
|
214
|
+
declare const OPENAI_API_KEY_ENV = "OPENAI_API_KEY";
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Replicate provider adapter.
|
|
218
|
+
*
|
|
219
|
+
* Builds a Vercel AI SDK `ImageModel` from `@ai-sdk/replicate`. Building a model
|
|
220
|
+
* performs no network I/O — the request only happens when `generateImage`
|
|
221
|
+
* invokes `model.doGenerate`.
|
|
222
|
+
*
|
|
223
|
+
* NOTE: Replicate's SDK names the credential option `apiToken` (not `apiKey`),
|
|
224
|
+
* and reads `REPLICATE_API_TOKEN` from the environment.
|
|
225
|
+
*/
|
|
226
|
+
|
|
227
|
+
/** Tier → Replicate model id (all tiers → FLUX 1.1 Pro Ultra for now). */
|
|
228
|
+
declare const REPLICATE_TIER_MODELS: Readonly<Record<ImageTier, string>>;
|
|
229
|
+
/** Env var read for the Replicate API token when `apiToken` is not in config. */
|
|
230
|
+
declare const REPLICATE_API_KEY_ENV = "REPLICATE_API_TOKEN";
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* fal provider adapter.
|
|
234
|
+
*
|
|
235
|
+
* Builds a Vercel AI SDK `ImageModel` from `@ai-sdk/fal`. Building a model
|
|
236
|
+
* performs no network I/O — the request only happens when `generateImage`
|
|
237
|
+
* invokes `model.doGenerate`. This is the lightweight fal *image* adapter; the
|
|
238
|
+
* richer `MotifServer` fal client (queue, upload, upscale, rmbg, video, tools)
|
|
239
|
+
* is a separate, later fold (design doc §8, phase 1d).
|
|
240
|
+
*
|
|
241
|
+
* ENDPOINT QUIRK (Phase 0): fal's gpt-image endpoint wants `image_size` as a
|
|
242
|
+
* STRING enum (e.g. "1024x1024"), passed at generate time via
|
|
243
|
+
* `providerOptions.fal.image_size` — NOT the AI SDK's generic `size` object.
|
|
244
|
+
* This adapter does NOT auto-inject it; callers pass `providerOptions` when they
|
|
245
|
+
* need a specific size. Example:
|
|
246
|
+
* img.generate({
|
|
247
|
+
* provider: "fal",
|
|
248
|
+
* tier: "balanced",
|
|
249
|
+
* prompt: "...",
|
|
250
|
+
* providerOptions: { fal: { image_size: "1024x1024" } },
|
|
251
|
+
* });
|
|
252
|
+
*/
|
|
253
|
+
|
|
254
|
+
/** Tier → fal model id. */
|
|
255
|
+
declare const FAL_TIER_MODELS: Readonly<Record<ImageTier, string>>;
|
|
256
|
+
/** Env var read for the fal key when `apiKey` is not supplied in config. */
|
|
257
|
+
declare const FAL_API_KEY_ENV = "FAL_KEY";
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Per-call cost tracking for the image layer.
|
|
261
|
+
*
|
|
262
|
+
* Preference order:
|
|
263
|
+
* 1. A cost surfaced by the provider on `result.providerMetadata` (most image
|
|
264
|
+
* providers do NOT surface one today, so this is usually absent).
|
|
265
|
+
* 2. A static per-model table, owned per-adapter (`priceUsdByModel`) and read
|
|
266
|
+
* from the provider registry (see sources in each adapter).
|
|
267
|
+
* 3. Unknown → `{ usd: 0, source: "unknown" }`.
|
|
268
|
+
*/
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Extract a provider-reported total cost from a `generateImage` result's
|
|
272
|
+
* `providerMetadata`, if the provider surfaces a numeric `cost`. The shape is
|
|
273
|
+
* `{ [provider]: { ...; cost?: number } }`. Returns undefined otherwise.
|
|
274
|
+
*/
|
|
275
|
+
declare function costFromProviderMetadata(providerMetadata: unknown): number | undefined;
|
|
276
|
+
/**
|
|
277
|
+
* Normalized per-call cost for a generation. Prefers a provider-metadata cost,
|
|
278
|
+
* then the static table (× image count), then unknown.
|
|
279
|
+
*/
|
|
280
|
+
declare function costForImages(provider: ImageProviderId, modelId: string, providerMetadata: unknown, imageCount: number): ImageCost;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* `@howells/motif-sdk/image` — provider-agnostic image generation + editing.
|
|
284
|
+
*
|
|
285
|
+
* ESM-only subpath export, built on the Vercel AI SDK image interface
|
|
286
|
+
* (`generateImage`, `@ai-sdk/*`). Additive to the fal-specific `MotifServer`
|
|
287
|
+
* surface; reuses the SDK's Result convention (`Result<T, MotifError>` — no
|
|
288
|
+
* thrown exceptions). Google (Gemini) is the only provider in Phase 1a.
|
|
289
|
+
*
|
|
290
|
+
* @example
|
|
291
|
+
* ```ts
|
|
292
|
+
* import { createMotifImage } from "@howells/motif-sdk/image";
|
|
293
|
+
*
|
|
294
|
+
* const img = createMotifImage({ defaultProvider: "google" });
|
|
295
|
+
* const r = await img.generate({ tier: "fast", prompt: "a bare concrete wall" });
|
|
296
|
+
* if (r.isOk()) console.log(r.value.images[0].mediaType, r.value.cost);
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
|
|
300
|
+
/** A model resolver: builds an AI SDK `ImageModel` for a (provider, model, key). */
|
|
301
|
+
type ResolveImageModel = (provider: ImageProviderId, modelId: string, apiKey?: string) => ImageModel;
|
|
302
|
+
/** Internal dependency-injection seam (default: real `generateImage` + adapters). */
|
|
303
|
+
interface MotifImageDeps {
|
|
304
|
+
generateImage?: typeof generateImage;
|
|
305
|
+
resolveModel?: ResolveImageModel;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Create a provider-agnostic image client.
|
|
309
|
+
*
|
|
310
|
+
* @param config - provider selection + keys (keys fall back to env).
|
|
311
|
+
* @param deps - INTERNAL testing seam. Defaults to the real AI SDK `generateImage`
|
|
312
|
+
* and the built-in provider adapters; tests inject fakes here to run offline.
|
|
313
|
+
*/
|
|
314
|
+
declare function createMotifImage(config?: MotifImageConfig, deps?: MotifImageDeps): MotifImageClient;
|
|
315
|
+
|
|
316
|
+
export { type EditImageOptions, FAL_API_KEY_ENV, FAL_TIER_MODELS, GOOGLE_API_KEY_ENV, GOOGLE_TIER_MODELS, type GenerateImageOptions, type ImageCost, type ImageCostSource, type ImageProviderAdapter, type ImageProviderId, type ImageTier, type MotifImageClient, type MotifImageConfig, type MotifImageDeps, type MotifImageFile, type MotifImageResult, OPENAI_API_KEY_ENV, OPENAI_TIER_MODELS, PROVIDERS, REPLICATE_API_KEY_ENV, REPLICATE_TIER_MODELS, type ResolveImageModel, costForImages, costFromProviderMetadata, createMotifImage, getProviderAdapter };
|