@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 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 google, openai, replicate, and fal. See [Image Layer](#image-layer-howellsmotif-sdkimage) below.
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 google, openai, replicate, and fal.
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: "google" });
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, optional mask -> image out)
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
- | `google` | `GOOGLE_GENERATIVE_AI_API_KEY` | Default provider; Gemini gen + edit |
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
- * (`google`, `openai`, `replicate`, `fal`); the type keeps an open union tail so
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 = "google" | "openai" | "replicate" | "fal" | (string & Record<never, never>);
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 `google`. */
51
+ /** Provider used when a call does not specify one. Defaults to `openrouter`. */
52
52
  defaultProvider?: ImageProviderId;
53
- /** Google provider overrides. `apiKey` falls back to `GOOGLE_GENERATIVE_AI_API_KEY`. */
54
- google?: {
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/google.d.ts
297
- /** Env var read for the Google API key when `apiKey` is not supplied in config. */
298
- export declare const GOOGLE_API_KEY_ENV = "GOOGLE_GENERATIVE_AI_API_KEY";
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. */