@fungi.computer/darkroom 0.0.0-stage → 0.1.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.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +315 -2
  3. package/dist/app-media-contracts.d.ts +499 -0
  4. package/dist/app-media-contracts.js +96 -0
  5. package/dist/browser-failure.d.ts +40 -0
  6. package/dist/browser-failure.js +39 -0
  7. package/dist/browser.d.ts +2 -0
  8. package/dist/browser.js +1 -0
  9. package/dist/cloudflare/provider.d.ts +13 -0
  10. package/dist/cloudflare/provider.js +140 -0
  11. package/dist/cloudflare/reference-images.d.ts +42 -0
  12. package/dist/cloudflare/reference-images.js +102 -0
  13. package/dist/cloudflare.d.ts +6 -0
  14. package/dist/cloudflare.js +4 -0
  15. package/dist/cost-estimate.d.ts +22 -0
  16. package/dist/cost-estimate.js +76 -0
  17. package/dist/effect.d.ts +2 -0
  18. package/dist/effect.js +2 -0
  19. package/dist/execution-error.d.ts +54 -0
  20. package/dist/execution-error.js +62 -0
  21. package/dist/failure.d.ts +2 -0
  22. package/dist/failure.js +8 -0
  23. package/dist/gateway-models.d.ts +23 -0
  24. package/dist/gateway-models.js +77 -0
  25. package/dist/gateway-wire.d.ts +16 -0
  26. package/dist/gateway-wire.js +22 -0
  27. package/dist/index.d.ts +361 -0
  28. package/dist/index.js +607 -0
  29. package/dist/media-binding.d.ts +291 -0
  30. package/dist/media-binding.js +149 -0
  31. package/dist/media-generation.d.ts +10 -0
  32. package/dist/media-generation.js +110 -0
  33. package/dist/media-operation-contract.d.ts +104 -0
  34. package/dist/media-operation-contract.js +29 -0
  35. package/dist/media-operation-do.d.ts +55 -0
  36. package/dist/media-operation-do.js +105 -0
  37. package/dist/media-operation-owner.d.ts +301 -0
  38. package/dist/media-operation-owner.js +903 -0
  39. package/dist/media-operation-storage.d.ts +81 -0
  40. package/dist/media-operation-storage.js +1 -0
  41. package/dist/media-operation.d.ts +33 -0
  42. package/dist/media-operation.js +230 -0
  43. package/dist/model-catalog.d.ts +456 -0
  44. package/dist/model-catalog.js +565 -0
  45. package/dist/neurons.d.ts +4 -0
  46. package/dist/neurons.js +4 -0
  47. package/dist/provenance-bytes.d.ts +3 -0
  48. package/dist/provenance-bytes.js +15 -0
  49. package/dist/provenance-export.d.ts +23 -0
  50. package/dist/provenance-export.js +103 -0
  51. package/dist/provenance-jpeg.d.ts +3 -0
  52. package/dist/provenance-jpeg.js +76 -0
  53. package/dist/provenance-png.d.ts +1 -0
  54. package/dist/provenance-png.js +21 -0
  55. package/dist/provenance-xmp.d.ts +2 -0
  56. package/dist/provenance-xmp.js +54 -0
  57. package/dist/provenance.d.ts +20 -0
  58. package/dist/provenance.js +58 -0
  59. package/dist/server.d.ts +158 -0
  60. package/dist/server.js +207 -0
  61. package/dist/video-providers/index.d.ts +14 -0
  62. package/dist/video-providers/index.js +14 -0
  63. package/dist/video-providers/minimax.d.ts +25 -0
  64. package/dist/video-providers/minimax.js +63 -0
  65. package/dist/video-providers/reference-encoding.d.ts +4 -0
  66. package/dist/video-providers/reference-encoding.js +11 -0
  67. package/dist/video-providers/types.d.ts +28 -0
  68. package/dist/video-providers/types.js +1 -0
  69. package/dist/video-providers/unified.d.ts +18 -0
  70. package/dist/video-providers/unified.js +52 -0
  71. package/docs/api/README.md +16 -0
  72. package/docs/api/app-media-contracts.md +507 -0
  73. package/docs/api/browser.md +126 -0
  74. package/docs/api/cloudflare.md +678 -0
  75. package/docs/api/effect.md +39 -0
  76. package/docs/api/index.md +1775 -0
  77. package/docs/api/modules.md +19 -0
  78. package/docs/api/provenance-export.md +114 -0
  79. package/docs/api/server.md +1672 -0
  80. package/package.json +110 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jamstack Consulting LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,316 @@
1
- # Temporary Holding Version
1
+ # Darkroom
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Generate an image, keep the output, and retry a failed save without paying for
4
+ another generation. Your host supplies identity, permissions, a provider and a
5
+ place to put the file.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install @fungi.computer/darkroom
11
+ npm install --save-dev wrangler @cloudflare/workers-types
12
+ ```
13
+
14
+ The root catalog and `server` work without a Workers runtime. The `cloudflare`
15
+ adapter runs on workerd, celld and Cloudflare with SQLite Durable Objects and
16
+ R2.
17
+
18
+ ## Quick start
19
+
20
+ Save this as `worker.ts`. TypeScript hosts use
21
+ `lib: ["ES2023", "ESNext.Disposable"]` and
22
+ `types: ["@cloudflare/workers-types/experimental"]` in their compiler options.
23
+ This local example uses a fixed provider that returns a one-pixel GIF, so you
24
+ can try the save and retry path without a model account. Replace `provider` with
25
+ your own `MediaProvider` for real generation.
26
+
27
+ ```ts
28
+ /// <reference types="@cloudflare/workers-types/experimental" resolution-mode="require" />
29
+ import { listMediaModels } from "@fungi.computer/darkroom";
30
+ import { createMediaService } from "@fungi.computer/darkroom/server";
31
+ import { MediaOperationDO } from "@fungi.computer/darkroom/cloudflare";
32
+
33
+ export { MediaOperationDO };
34
+
35
+ type Bindings = {
36
+ MEDIA_OPERATION: DurableObjectNamespace<MediaOperationDO>;
37
+ APP_ARTIFACTS: R2Bucket;
38
+ OUTPUTS: R2Bucket;
39
+ };
40
+
41
+ export default {
42
+ async fetch(request: Request, env: Bindings): Promise<Response> {
43
+ const service = createMediaService({
44
+ scope: {
45
+ actorId: "local-owner",
46
+ namespace: "studio",
47
+ owner: "illustrator",
48
+ destinationId: "outputs",
49
+ },
50
+ operations: env.MEDIA_OPERATION,
51
+ provider: async () => ({
52
+ bytes: Uint8Array.from(
53
+ atob("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"),
54
+ (character) => character.charCodeAt(0),
55
+ ),
56
+ format: "gif",
57
+ }),
58
+ metering: "unmetered",
59
+ destination: {
60
+ authorize: async () => {},
61
+ write: async (path, bytes) => {
62
+ await env.OUTPUTS.put(path, bytes);
63
+ },
64
+ },
65
+ });
66
+ if (new URL(request.url).pathname === "/catalog")
67
+ return Response.json(listMediaModels());
68
+ return service.fetch(request);
69
+ },
70
+ };
71
+ ```
72
+
73
+ Save this as `wrangler.json`:
74
+
75
+ ```json
76
+ {
77
+ "name": "darkroom-example",
78
+ "main": "worker.ts",
79
+ "compatibility_date": "2026-09-02",
80
+ "compatibility_flags": ["nodejs_compat"],
81
+ "durable_objects": {
82
+ "bindings": [
83
+ { "name": "MEDIA_OPERATION", "class_name": "MediaOperationDO" }
84
+ ]
85
+ },
86
+ "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MediaOperationDO"] }],
87
+ "r2_buckets": [
88
+ { "binding": "APP_ARTIFACTS", "bucket_name": "darkroom-retained" },
89
+ { "binding": "OUTPUTS", "bucket_name": "darkroom-output" }
90
+ ]
91
+ }
92
+ ```
93
+
94
+ Run `npx wrangler dev --local`, then:
95
+
96
+ ```sh
97
+ curl http://localhost:8787/models
98
+ curl http://localhost:8787/generate \
99
+ -H 'Content-Type: application/json' -H 'X-Request-Id: first-picture' \
100
+ -d '{"path":"picture.gif","settings":{"model":"@cf/black-forest-labs/flux-1-schnell","prompt":"a quiet greenhouse","width":1024,"height":768}}'
101
+ ```
102
+
103
+ Repeat the same request to replay its saved result. Change the input with the
104
+ same ID and the service returns 409. This example admits one local owner. Add
105
+ your host's authentication and destination authorization before serving others.
106
+
107
+ ## Core concepts
108
+
109
+ The host chooses `actorId`, `namespace`, `owner` and `destinationId`. Request
110
+ bodies cannot choose that authority. A durable operation captures its identity
111
+ and input, retains provider bytes before saving, and reuses those bytes when a
112
+ save fails. An ambiguous provider execution is never silently dispatched again.
113
+
114
+ Declare `metering: "unmetered"` when your host pays its provider directly. A
115
+ metered host supplies idempotent `admit` and `settle` functions plus its own
116
+ `refusalMessages` for insufficient credit, daily limits and denied spending.
117
+ Quotes are estimates. Your metering owner decides the customer price and
118
+ settlement.
119
+
120
+ A recognized provider safety refusal returns HTTP 400 with
121
+ `code: "SAFETY_FILTER_REFUSED"`, `stage: "provider"`, and `retryable: false`.
122
+ The public message asks you to reword the prompt; provider details stay
123
+ redacted. Nothing is saved or charged. The owner retains only the bounded
124
+ refusal reason, so same-ID replay and background video polling preserve that
125
+ verdict without calling the provider again. Workers AI's observed NSFW-input
126
+ refusal and
127
+ [Veo's documented input-policy refusal](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/video/responsible-ai-and-usage-guidelines)
128
+ are classified; an error number by itself is not a safety verdict.
129
+
130
+ Pre-launch operation stores must use the current fresh schema. Hosts reset old
131
+ disposable stores; Darkroom does not migrate or repair their missing refusal
132
+ fact.
133
+
134
+ Video generation runs through the operation's alarm. A 202 response tells you to
135
+ retry the same request ID after the supplied `Retry-After` delay.
136
+
137
+ ## API reference
138
+
139
+ | Import | Purpose |
140
+ | ---------------------------------------------- | ------------------------------------------------------------------------------ |
141
+ | `@fungi.computer/darkroom` | Model catalog, settings, references, provider wire shapes and cost estimates. |
142
+ | `@fungi.computer/darkroom/app-media-contracts` | Runtime-neutral App catalog, destination and completed-generation schemas. |
143
+ | `@fungi.computer/darkroom/server` | Hono service, typed generation, metering and the portable SQL operation owner. |
144
+ | `@fungi.computer/darkroom/cloudflare` | Native operation DO, Workers AI provider and Images fitters. |
145
+ | `@fungi.computer/darkroom/provenance` | Lossless public image downloads with private metadata removed. |
146
+
147
+ The App-facing JSON protocol and page handles live in
148
+ `@fungi.computer/agar/media-json`. Darkroom owns host execution and its
149
+ `@fungi.computer/darkroom/browser` HTTP failure decoder.
150
+
151
+ The App contract entry exports `mediaModelsSchema`, `destinationSchema` and
152
+ `appMediaGenerationResultSchema`. The catalog bound is the maintained model
153
+ count; the host still filters models after decoding. Pending App replies use
154
+ Agar's public `pendingSchema`. `appMediaModelsOutcomeSchema` and
155
+ `appMediaGenerationOutcomeSchema` include the broker's emitted refusal payloads:
156
+ closed admission codes, the Media attachment-budget refusal, the existing
157
+ `agentAppMediaLibraryFailureSchema`, and `appMediaBrokerFailureSchema`. The
158
+ latter preserves the broker projection: HTTP `code` becomes `error`, HTTP
159
+ `error` becomes `message`, and `stage` and `retryable` stay unchanged. HTTP
160
+ failures and transport envelopes are separate representations and do not decode
161
+ as these outcomes.
162
+
163
+ See the [generated API](docs/api/README.md) for signatures and host ports. The
164
+ Cloudflare entry needs the native Workers type declarations in a TypeScript
165
+ host. The other entries use ordinary platform web types.
166
+
167
+ ## Examples
168
+
169
+ For Workers AI, use `createWorkersAiMediaProvider(env.AI)` from `cloudflare` and
170
+ add an `AI` binding to your host. `createImagesReferenceFitter` prepares bounded
171
+ reference images through an Images binding. Your host authorizes each reference
172
+ read.
173
+
174
+ `stripPrivateProvenance(bytes)` from `provenance` removes prompt and lineage
175
+ metadata while preserving the AI marker, print ID and image encoding. It returns
176
+ bytes, MIME type and filename extension. The public XMP format uses
177
+ `https://fungi.computer/ns/provenance/1.0/`.
178
+
179
+ ## Limits
180
+
181
+ References are bounded to four files, 512 pixels per side and 8 MiB per image.
182
+ Still-image output and public downloads are bounded to 32 MiB. Catalog models
183
+ may impose tighter limits. Video output has its own 64 MiB provider bound.
184
+
185
+ Private-provenance export supports PNG, JPEG and WebP. GIF without XMP remains
186
+ index-only. Signed, malformed and unsupported images return a typed refusal. The
187
+ original file is never changed.
188
+
189
+ ## Contribute and license
190
+
191
+ MIT. See [LICENSE](LICENSE).
192
+
193
+ The `server` export provides the Promise `generateMedia(host, request, signal?)`
194
+ boundary and the relative HTTP service. Hosts composing Effect programs import
195
+ `runMediaGeneration` from `@fungi.computer/darkroom/effect`. Its advanced
196
+ declarations require the host's Effect environment; ordinary Workers consumers
197
+ need only the native Workers declarations.
198
+
199
+ ## Video selections
200
+
201
+ The root export owns `mediaVideoSelectionSchema`, `validateMediaVideoSelection`,
202
+ `MediaVideoSelectionInput` and `MediaVideoSelection`. Apps select paths; their
203
+ host supplies reference authority and the MP4 destination. For example:
204
+
205
+ ```ts
206
+ import {
207
+ estimateVideoMilliunits,
208
+ validateMediaVideoSelection,
209
+ } from "@fungi.computer/darkroom";
210
+
211
+ const selection = validateMediaVideoSelection({
212
+ model: "bytedance/seedance-2.0-mini",
213
+ prompt: "Fern fronds unfolding in morning light",
214
+ resolution: "720p",
215
+ aspectRatio: "16:9",
216
+ durationSeconds: 5,
217
+ references: [{ path: "fern.png", role: "first-frame" }],
218
+ });
219
+ const providerCostMilliunits = estimateVideoMilliunits(selection);
220
+ ```
221
+
222
+ The strict input fields are `model`, `prompt`, optional `resolution`,
223
+ `aspectRatio`, `durationSeconds`, `generateAudio`, and optional `references`.
224
+ Each reference is `{path, role}`; role is `first-frame`, `last-frame`, or
225
+ `reference`. Normalization returns nominal `width`/`height`, explicit
226
+ resolution, aspect and duration, and the same path-only references. The host
227
+ adds its admitted `computerId` before calling the existing service. A single
228
+ untagged reference in that host service is first-frame mode, preserving the
229
+ existing Workflow caller. Unsupported combinations throw `MediaSettingsError`;
230
+ the service returns `400 INVALID_ARGUMENT` before billing admission or dispatch.
231
+
232
+ The sole model catalog describes these verified Unified AI contracts: Seedance
233
+ and Veo checked 2026-10-11, MiniMax H3 Max checked 2026-10-14. They run through
234
+ Workers `AI.run` with unprefixed IDs. Cloudflare's Workers AI `@cf/` catalog
235
+ does not supply these video IDs.
236
+
237
+ | Model | Resolution | Duration | Reference modes | Provider USD / output second |
238
+ | --------------------------------------------------------------------------------------------- | ------------------------ | ---------------------------- | ------------------------------------------------------------------ | --------------------------------------------------------- |
239
+ | [Seedance 2.0](https://developers.cloudflare.com/ai/models/bytedance/seedance-2.0/) | 480p / 720p / 1080p / 4k | integers 4–12; default 5 | first frame; first + last; 1–4 reference images except at 1080p | .07 / .15 / .37 / .78 |
240
+ | [Seedance 2.0 Fast](https://developers.cloudflare.com/ai/models/bytedance/seedance-2.0-fast/) | 480p / 720p | integers 4–12; default 5 | first frame; first + last; 1–4 reference images | .06 / .12 |
241
+ | [Seedance 2.0 Mini](https://developers.cloudflare.com/ai/models/bytedance/seedance-2.0-mini/) | 480p / 720p | integers 4–12; default 5 | first frame; first + last; 1–4 reference images | .04 / .09 |
242
+ | [Veo 3.1](https://developers.cloudflare.com/ai/models/google/veo-3.1/) | 720p / 1080p | exactly 4 / 6 / 8; default 6 | one first image | .20 silent / .40 audio at either resolution |
243
+ | [Veo 3.1 Fast](https://developers.cloudflare.com/ai/models/google/veo-3.1-fast/) | 720p / 1080p | exactly 4 / 6 / 8; default 6 | one first image | 720p .08 silent / .10 audio; 1080p .10 silent / .12 audio |
244
+ | [MiniMax H3 Max](https://developers.cloudflare.com/ai/models/minimax/h3-max/) | 480p / 768p | integers 5–15; default 5 | first frame; first + last; 1–4 reference images, either resolution | .05 / .08 (no documented audio surcharge) |
245
+
246
+ Seedance and Veo default to 720p and 16:9; MiniMax H3 Max defaults to 768p and
247
+ 16:9. Seedance aspects are 16:9, 4:3, 1:1, 3:4, 9:16, 21:9, and 9:21, with fixed
248
+ 24 FPS. Veo aspects are 16:9, 9:16 and 1:1. MiniMax H3 Max aspects are 16:9,
249
+ 4:3, 1:1, 3:4, 9:16 and 21:9 (no 9:21; its documented `adaptive` selector, which
250
+ follows the input image's own aspect instead of a fixed box, has no nominal
251
+ pixel size and is not exposed by this catalog). Seedance prompts are at most
252
+ 2,000 characters; Veo uses Darkroom's retained 2,048-character host limit
253
+ because the provider page states no prompt maximum; MiniMax H3 Max's own limit
254
+ is 7,000 but Darkroom's 2,048-character host cap binds first. Paths are trimmed,
255
+ nonblank and at most 4,096 characters; references are bounded at four overall
256
+ and refined per model (MiniMax H3 Max's own page documents up to nine, which
257
+ this four-reference host ceiling cannot reach). Last frame requires exactly one
258
+ first frame. Frame mode cannot mix with reference-image mode. Seedance's first
259
+ image can override the requested aspect; returned width/height are nominal
260
+ selectors, not measured MP4 dimensions.
261
+
262
+ Veo audio defaults to true and is always explicit in the provider request and
263
+ quote. Seedance leaves `generate_audio` unset when `generateAudio` is omitted,
264
+ because its audio default is undocumented; its non-video-input rates are the
265
+ same for either audio choice. MiniMax H3 Max has no audio field at all: its
266
+ catalog entry carries the same silent and audio rate, and `gatewayInput` throws
267
+ `MediaSettingsError` if a caller sets `generateAudio` explicitly, rather than
268
+ silently dropping the choice. `estimateVideoMilliunits` and the gateway
269
+ estimator read the catalog prices: 1 USD = 100,000 milliunits, 1 cent = 1,000.
270
+ `mediaUnits` returns `{unit: "video_second", units: durationSeconds}`. The host
271
+ still owns admission, settlement, markup and refusal copy.
272
+
273
+ Reference limits follow the explicit Seedance parameter section's four-image
274
+ grammar; its overview's larger counts remain unsupported. Veo 4k pricing does
275
+ not establish a supported 4k input, and Seedance 2.5's duration grammar remains
276
+ unverified. Video/audio references and undocumented options are refused, never
277
+ silently dropped. Seedance frames and references become data URIs; Veo's
278
+ `image_input` is plain base64; MiniMax H3 Max's are data URIs inside a
279
+ multimodal `content[]` array (`{type: "image_url", image_url: {url}, role}`),
280
+ alongside the leading `{type: "text", text: prompt}` entry — a different
281
+ provider body than Seedance or Veo's flat `prompt`/`image` fields, documented
282
+ only by the raw request/response JSON schemas the Cloudflare page links, not its
283
+ prose parameter list. MiniMax H3 Max also sends its resolution selector
284
+ uppercase (`480P`/`768P`) where every other model sends it lowercase. Every
285
+ admitted image is translated, preserving its role. All successful responses use
286
+ the existing bounded HTTPS MP4 fetch and retention path (64 MiB maximum).
287
+ Seedance and Veo answer with the Unified gateway's normalized
288
+ `{state: "Completed", result: {video}}`; MiniMax H3 Max instead answers with its
289
+ own documented `{task: {status, content: {url}}}` wrapper, decoded separately
290
+ and treated as failed unless `status` is exactly `"succeeded"`. The provider
291
+ returns `{bytes, format: "mp4"}`; the service returns
292
+ `{bytes, costMilliunits, path, format, height, model, width}` on completion.
293
+ While the alarm is running, HTTP returns 202 with
294
+ `{status: "pending", retryAfterSeconds: 5}`. Retry the same request ID,
295
+ destination and settings to recover the retained result. The host chooses the
296
+ output path, such as `prints-<requestId>.mp4`; Darkroom adds no output-naming
297
+ rule or human-review gate.
298
+
299
+ ### App MEDIA binding
300
+
301
+ The shared `@fungi.computer/darkroom/server` binding grammar and reply decoder
302
+ are used by Hub and standalone Oyster. The internal generation command carries
303
+ its image/video kind, stable request ID and App-owned library folder. Hosts
304
+ attach the admitted Computer to reference paths, read and fit those references,
305
+ and use `createMediaService` for generation, retention, replay and saving.
306
+ Guests cannot supply a principal or reference Computer ID.
307
+
308
+ Image encoding is model-native when `outputFormat` is omitted. An explicit PNG,
309
+ JPEG or WebP pin must be supported by the model; the saved result reports its
310
+ actual encoding. Catalogs retain reference support declared by the model and
311
+ available through that host. Unsupported reference selections are typed
312
+ `INVALID_ARGUMENT` settings refusals before dispatch.
313
+
314
+ Runtime-neutral App definitions import the shared binding schemas from
315
+ `@fungi.computer/darkroom/media-binding`; the server entry also re-exports them
316
+ for host composition.