@howells/motif-sdk 4.0.0 → 5.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 +40 -8
- package/dist/image.d.ts +8 -8
- package/dist/image.js +262 -135
- package/dist/index.cjs +564 -152
- package/dist/index.d.cts +79 -63
- package/dist/index.d.ts +79 -63
- package/dist/index.js +564 -152
- package/package.json +11 -12
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ npm install @howells/motif-sdk
|
|
|
10
10
|
|
|
11
11
|
## Run a Task
|
|
12
12
|
|
|
13
|
-
The Task client, `createMotif`, is the fal surface: name a Task and, optionally, a Tier, and Motif chooses the Model, builds its request and runs it on fal. `createMotifImage` (`@howells/motif-sdk/image`) is the provider-agnostic generate/edit layer across
|
|
13
|
+
The Task client, `createMotif`, is the fal surface: name a Task and, optionally, a Tier, and Motif chooses the Model, builds its request and runs it on fal. `createMotifImage` (`@howells/motif-sdk/image`) is the provider-agnostic generate/edit layer across openrouter, openai, replicate, and fal. See [Image Layer](#image-layer-howellsmotif-sdkimage) below.
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
16
|
import { createMotif } from "@howells/motif-sdk";
|
|
@@ -32,6 +32,8 @@ console.log(result.value.model, result.value.files[0]?.url, result.value.cost);
|
|
|
32
32
|
|
|
33
33
|
Every Task function returns `Result<TaskOutput, MotifError>` from `neverthrow` and does not throw for fal request failures; check `isErr()` / `isOk()`.
|
|
34
34
|
|
|
35
|
+
Pass `onProgress(status, queuePosition)` to hear fal's queue state while a run waits: `"queued"` with its place in line, then `"in_progress"`, then `"completed"`. Passing it sends the run through fal's queue, since only the queue reports state. Models without streaming report state, not a percentage.
|
|
36
|
+
|
|
35
37
|
## Plan Without Calling fal
|
|
36
38
|
|
|
37
39
|
`plan(task, input, { dryRun: true })` resolves the Model and returns the endpoint, body and projected cost with no I/O and no key.
|
|
@@ -51,11 +53,42 @@ if (plan.isOk()) {
|
|
|
51
53
|
}
|
|
52
54
|
```
|
|
53
55
|
|
|
56
|
+
## Stream a Task
|
|
57
|
+
|
|
58
|
+
`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.
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const controller = new AbortController();
|
|
62
|
+
const opened = await motif.stream(
|
|
63
|
+
"generate",
|
|
64
|
+
{
|
|
65
|
+
model: "gpt2",
|
|
66
|
+
prompt: "editorial product photo",
|
|
67
|
+
references: ["https://example.com/reference.png"],
|
|
68
|
+
},
|
|
69
|
+
{ signal: controller.signal, timeout: 300_000 }
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
if (opened.isErr()) throw opened.error;
|
|
73
|
+
for await (const result of opened.value.events) {
|
|
74
|
+
if (result.isErr()) throw result.error;
|
|
75
|
+
const event = result.value;
|
|
76
|
+
if (event.type === "images") render(event.files);
|
|
77
|
+
if (event.type === "progress") showProgress(event.progress, event.message);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Events use `images`, `progress` or `provider` types. Images carry the same `TaskFile` URL shape as ordinary Task outputs, including data URIs. Progress fractions are normalised only when explicitly between zero and one. The raw provider payload remains available as `raw` or `data`, and SSE event/id metadata is preserved. Preview images depend on the model; an images event is not labelled preview or final, and transport EOF does not prove generation success.
|
|
82
|
+
|
|
83
|
+
The handle exposes `plan`, optional `requestId` and `abort()`. Consume its events once. Abort, iterator exit and the overall deadline close local consumption; this does not guarantee cancellation of provider work or a refund. There is no queue submission, reconnection or retry. `plan()` remains a normal execution plan, so its `queued` flag describes `run()`, not `stream()`.
|
|
84
|
+
|
|
85
|
+
For a local checkout, `node packages/motif-sdk/examples/stream.mjs --dry-run` prints the resolved request without a provider call. Running it without `--dry-run` spends provider credits; it requires `FAL_KEY` and a built SDK. The runner is a repository example, not a packaged public API.
|
|
86
|
+
|
|
54
87
|
## Main Exports
|
|
55
88
|
|
|
56
|
-
- `createMotif` - the Task client: one function per Task plus `run`, `plan`, `upload` and `deletePayloads`.
|
|
89
|
+
- `createMotif` - the Task client: one function per Task plus `run`, `stream`, `plan`, `upload` and `deletePayloads`.
|
|
57
90
|
- `TASKS`, `TASK_IDS`, `TIERS`, `resolveTask`, `modelProfile`, `tierChangesChoice` - the Task registry and Model resolution.
|
|
58
|
-
- `createMotifImage` (`@howells/motif-sdk/image`) - provider-agnostic generate/edit/best-of-N across
|
|
91
|
+
- `createMotifImage` (`@howells/motif-sdk/image`) - provider-agnostic generate/edit/best-of-N across openrouter, openai, replicate, and fal.
|
|
59
92
|
- `ASPECT_RATIOS`, `RESOLUTIONS`, `FORMAT_PRESETS` - shared sizing metadata.
|
|
60
93
|
- `LOOKS`, `CREATIVE_TAXONOMY`, `enrichPrompt`, `validateCreativeDirection` - house looks and moods.
|
|
61
94
|
- `formatCost`, `sumCosts` - cost formatting and totals.
|
|
@@ -64,7 +97,7 @@ if (plan.isOk()) {
|
|
|
64
97
|
|
|
65
98
|
## Common Types
|
|
66
99
|
|
|
67
|
-
- `MotifClient`, `MotifClientConfig`, `TaskInput`, `TaskOutput`, `TaskPlan`, `TaskFile`
|
|
100
|
+
- `MotifClient`, `MotifClientConfig`, `TaskInput`, `TaskOutput`, `TaskPlan`, `TaskFile`, `TaskStream`, `TaskStreamEvent`, `TaskStreamOptions`
|
|
68
101
|
- `TaskId`, `Tier`, `TaskDefinition`, `RankedModel`, `TaskRequest`, `TaskResolution`, `ModelProfile`
|
|
69
102
|
- `AspectRatio`, `Resolution`, `CustomImageSize`, `ImageSizeBounds`, `MotifError`
|
|
70
103
|
|
|
@@ -79,7 +112,7 @@ npm install @howells/motif-sdk
|
|
|
79
112
|
```ts
|
|
80
113
|
import { createMotifImage } from "@howells/motif-sdk/image";
|
|
81
114
|
|
|
82
|
-
const img = createMotifImage({ defaultProvider: "
|
|
115
|
+
const img = createMotifImage({ defaultProvider: "openrouter" });
|
|
83
116
|
|
|
84
117
|
// text -> image
|
|
85
118
|
const generated = await img.generate({
|
|
@@ -88,12 +121,11 @@ const generated = await img.generate({
|
|
|
88
121
|
aspectRatio: "1:1",
|
|
89
122
|
});
|
|
90
123
|
|
|
91
|
-
// multi-image edit (images + instruction
|
|
124
|
+
// multi-image edit (images + instruction -> image out; masks need the openai provider)
|
|
92
125
|
const edited = await img.edit({
|
|
93
126
|
model: "gemini-3.1-flash-image-preview",
|
|
94
127
|
images: [roomBytes, tileBytes],
|
|
95
128
|
instruction: "Apply the oak texture from image 2 onto the wall in image 1.",
|
|
96
|
-
mask: surfaceMaskBytes,
|
|
97
129
|
});
|
|
98
130
|
|
|
99
131
|
if (edited.isOk()) {
|
|
@@ -110,7 +142,7 @@ Four providers are implemented, each reading its own API key from the environmen
|
|
|
110
142
|
|
|
111
143
|
| Provider | Env var | Notes |
|
|
112
144
|
| --- | --- | --- |
|
|
113
|
-
| `
|
|
145
|
+
| `openrouter` | `OPENROUTER_API_KEY` | Default provider; Gemini gen + edit through OpenRouter (`google/*`), no masks |
|
|
114
146
|
| `openai` | `OPENAI_API_KEY` | GPT Image 2.5 Flare and Sunburst |
|
|
115
147
|
| `replicate` | `REPLICATE_API_TOKEN` | flux-1.1-pro-ultra |
|
|
116
148
|
| `fal` | `FAL_KEY` | fal-hosted adapter |
|
package/dist/image.d.ts
CHANGED
|
@@ -28,10 +28,10 @@ declare class MotifError extends Error {
|
|
|
28
28
|
//#region src/image/types.d.ts
|
|
29
29
|
/**
|
|
30
30
|
* Image provider id. All four Phase 1b adapters are implemented
|
|
31
|
-
* (`
|
|
31
|
+
* (`openrouter`, `openai`, `replicate`, `fal`); the type keeps an open union tail so
|
|
32
32
|
* further adapters can slot in without a breaking type change.
|
|
33
33
|
*/
|
|
34
|
-
type ImageProviderId = "
|
|
34
|
+
type ImageProviderId = "openrouter" | "openai" | "replicate" | "fal" | (string & Record<never, never>);
|
|
35
35
|
/** Source that produced a normalized per-call cost. */
|
|
36
36
|
type ImageCostSource = "provider-metadata" | "table" | "unknown";
|
|
37
37
|
/** Normalized per-call spend attached to every result. */
|
|
@@ -48,10 +48,10 @@ interface ImageCost {
|
|
|
48
48
|
* credential `apiToken`, not `apiKey`.
|
|
49
49
|
*/
|
|
50
50
|
interface MotifImageConfig {
|
|
51
|
-
/** Provider used when a call does not specify one. Defaults to `
|
|
51
|
+
/** Provider used when a call does not specify one. Defaults to `openrouter`. */
|
|
52
52
|
defaultProvider?: ImageProviderId;
|
|
53
|
-
/**
|
|
54
|
-
|
|
53
|
+
/** OpenRouter provider overrides. `apiKey` falls back to `OPENROUTER_API_KEY`. */
|
|
54
|
+
openrouter?: {
|
|
55
55
|
apiKey?: string;
|
|
56
56
|
};
|
|
57
57
|
/** OpenAI provider overrides. `apiKey` falls back to `OPENAI_API_KEY`. */
|
|
@@ -293,9 +293,9 @@ export declare const PROVIDERS: Record<ImageProviderId, ImageProviderAdapter>;
|
|
|
293
293
|
*/
|
|
294
294
|
export declare function getProviderAdapter(provider: ImageProviderId): ImageProviderAdapter;
|
|
295
295
|
//#endregion
|
|
296
|
-
//#region src/image/
|
|
297
|
-
/** Env var read for the
|
|
298
|
-
export declare const
|
|
296
|
+
//#region src/image/openrouter.d.ts
|
|
297
|
+
/** Env var read for the OpenRouter API key when `apiKey` is not supplied in config. */
|
|
298
|
+
export declare const OPENROUTER_API_KEY_ENV = "OPENROUTER_API_KEY";
|
|
299
299
|
//#endregion
|
|
300
300
|
//#region src/image/openai.d.ts
|
|
301
301
|
/** Env var read for the OpenAI API key when `apiKey` is not supplied in config. */
|