@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
@@ -0,0 +1,103 @@
1
+ import { DOMParser } from "@xmldom/xmldom";
2
+ import ExifReader from "exifreader";
3
+ import { createRiffContainer, parseRiff } from "@fungi.computer/ts-webp";
4
+ import { replacePngXmp } from "./provenance-png.js";
5
+ import { replaceJpegXmp } from "./provenance-jpeg.js";
6
+ import { publicProvenancePacket } from "./provenance-xmp.js";
7
+ /** The admitted Computer file read and generated output share this launch bound. */
8
+ export const maximumPrintDownloadBytes = 32 * 1024 * 1024;
9
+ /** A refusal to export unsafe, unsupported or oversized image bytes. */
10
+ export class ProvenanceExportError extends Error {
11
+ reason;
12
+ /** Stable export refusal name. */
13
+ name = "ProvenanceExportError";
14
+ constructor(
15
+ /** Bounded refusal reason. */
16
+ reason, options) {
17
+ super(`Print export refused: ${reason}`, options);
18
+ this.reason = reason;
19
+ }
20
+ }
21
+ function replaceWebpXmp(bytes, xmp) {
22
+ const chunks = parseRiff(bytes);
23
+ // The maintained parser tolerates truncation for inspection. Export must not
24
+ // turn a partial file into an apparently successful download.
25
+ if (chunks.some((chunk) => chunk.size !== chunk.data.length) ||
26
+ chunks.reduce((length, chunk) => length + 8 + chunk.size + (chunk.size % 2), 12) !== bytes.length)
27
+ throw new Error("Incomplete extended WebP");
28
+ if (!chunks.some((chunk) => chunk.fourCC === "XMP "))
29
+ return bytes;
30
+ if (!chunks.some((chunk) => chunk.fourCC === "VP8X"))
31
+ throw new Error("XMP without extended WebP header");
32
+ let inserted = false;
33
+ return createRiffContainer(chunks.flatMap((chunk) => {
34
+ if (chunk.fourCC !== "XMP ")
35
+ return [
36
+ {
37
+ fourCC: chunk.fourCC,
38
+ data: chunk.data,
39
+ },
40
+ ];
41
+ if (inserted)
42
+ return [];
43
+ inserted = true;
44
+ return [
45
+ {
46
+ fourCC: "XMP ",
47
+ data: new TextEncoder().encode(xmp),
48
+ },
49
+ ];
50
+ }));
51
+ }
52
+ /** Lossless browser export. Never mutates the input or falls back to private bytes. */
53
+ export async function stripPrivateProvenance(bytes) {
54
+ if (bytes.length > maximumPrintDownloadBytes)
55
+ throw new ProvenanceExportError("too-large");
56
+ // Match the embed owner's conservative signed-image policy. This is not
57
+ // signature validation, and stripping never silently invalidates a signature.
58
+ const text = new TextDecoder("latin1").decode(bytes);
59
+ if (text.includes("c2pa") || text.includes("caBX"))
60
+ throw new ProvenanceExportError("signed-image");
61
+ try {
62
+ const metadata = await ExifReader.loadView(new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength), {
63
+ expanded: true,
64
+ async: true,
65
+ domParser: new DOMParser(),
66
+ includeTags: { xmp: true, png: true, file: true },
67
+ decompress: { maxDecompressedSize: maximumPrintDownloadBytes },
68
+ });
69
+ const format = metadata.file?.FileType?.value;
70
+ if (format !== "png" &&
71
+ format !== "jpeg" &&
72
+ format !== "webp" &&
73
+ format !== "gif")
74
+ throw new ProvenanceExportError("unsupported-format");
75
+ const mime = {
76
+ type: `image/${format}`,
77
+ extension: format === "jpeg" ? "jpg" : format,
78
+ };
79
+ if (format === "gif" && !metadata.xmp)
80
+ return { bytes, ...mime };
81
+ const packet = publicProvenancePacket(metadata.xmp?._raw ?? metadata.pngText?.["XML:com.adobe.xmp"]?.value);
82
+ const output = format === "png"
83
+ ? replacePngXmp(bytes, packet)
84
+ : format === "jpeg"
85
+ ? replaceJpegXmp(bytes, packet)
86
+ : format === "webp"
87
+ ? replaceWebpXmp(bytes, packet)
88
+ : undefined;
89
+ if (!output)
90
+ throw new ProvenanceExportError("unsupported-format");
91
+ if (output.length > maximumPrintDownloadBytes)
92
+ throw new ProvenanceExportError("too-large");
93
+ return {
94
+ bytes: output,
95
+ ...mime,
96
+ };
97
+ }
98
+ catch (cause) {
99
+ if (cause instanceof ProvenanceExportError)
100
+ throw cause;
101
+ throw new ProvenanceExportError("invalid-image", { cause });
102
+ }
103
+ }
@@ -0,0 +1,3 @@
1
+ /** Narrow JPEG splice exception: adr-jpeg-provenance-splice. Never decode or
2
+ * rewrite scan data; walk bounded header segments and preserve their bytes. */
3
+ export declare function replaceJpegXmp(bytes: Uint8Array, xmp: string): Uint8Array;
@@ -0,0 +1,76 @@
1
+ import { joinBytes, startsWithBytes } from "./provenance-bytes.js";
2
+ const jpegXmpSignature = "http://ns.adobe.com/xap/1.0/\0";
3
+ const jpegExtendedXmpSignature = "http://ns.adobe.com/xmp/extension/\0";
4
+ /** Narrow JPEG splice exception: adr-jpeg-provenance-splice. Never decode or
5
+ * rewrite scan data; walk bounded header segments and preserve their bytes. */
6
+ export function replaceJpegXmp(bytes, xmp) {
7
+ const source = bytes;
8
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
9
+ if (source.length < 4 ||
10
+ view.getUint16(0) !== 0xffd8 ||
11
+ view.getUint16(source.length - 2) !== 0xffd9)
12
+ throw new Error("Invalid JPEG envelope");
13
+ const payload = new TextEncoder().encode(`${jpegXmpSignature}${xmp}`);
14
+ if (payload.length > 65_533)
15
+ throw new Error("XMP exceeds one JPEG APP1 segment");
16
+ const header = new Uint8Array(4);
17
+ const headerView = new DataView(header.buffer);
18
+ headerView.setUint16(0, 0xffe1);
19
+ headerView.setUint16(2, payload.length + 2);
20
+ const xmpSegment = joinBytes([header, payload]);
21
+ const segments = [source.subarray(0, 2)];
22
+ let insertion = 1;
23
+ let leading = true;
24
+ let hasFrame = false;
25
+ let offset = 2;
26
+ while (offset + 4 <= source.length) {
27
+ const start = offset;
28
+ if (source[offset] !== 0xff)
29
+ throw new Error("Invalid JPEG marker");
30
+ while (source[offset] === 0xff)
31
+ offset++;
32
+ const marker = source[offset];
33
+ if (marker === undefined ||
34
+ marker === 0 ||
35
+ marker === 0xd8 ||
36
+ marker === 0xd9)
37
+ throw new Error("JPEG ended before its scan");
38
+ if (marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) {
39
+ segments.push(source.subarray(start, offset + 1));
40
+ offset++;
41
+ leading = false;
42
+ continue;
43
+ }
44
+ if (offset + 3 > source.length)
45
+ throw new Error("Truncated JPEG length");
46
+ const length = view.getUint16(offset + 1);
47
+ const end = offset + 1 + length;
48
+ if (length < 2 || end > source.length)
49
+ throw new Error("Truncated JPEG segment");
50
+ if (marker >= 0xc0 &&
51
+ marker <= 0xcf &&
52
+ ![0xc4, 0xc8, 0xcc].includes(marker))
53
+ hasFrame = true;
54
+ if (marker === 0xda) {
55
+ if (!hasFrame)
56
+ throw new Error("JPEG has no frame");
57
+ segments.splice(insertion, 0, xmpSegment);
58
+ segments.push(source.subarray(start));
59
+ return joinBytes(segments);
60
+ }
61
+ const data = source.subarray(offset + 3, end);
62
+ const isXmp = marker === 0xe1 &&
63
+ [jpegXmpSignature, jpegExtendedXmpSignature].some((signature) => startsWithBytes(data, signature));
64
+ if (!isXmp) {
65
+ segments.push(source.subarray(start, end));
66
+ if (leading &&
67
+ (marker === 0xe0 ||
68
+ (marker === 0xe1 && startsWithBytes(data, "Exif\0\0"))))
69
+ insertion = segments.length;
70
+ else
71
+ leading = false;
72
+ }
73
+ offset = end;
74
+ }
75
+ throw new Error("JPEG has no scan");
76
+ }
@@ -0,0 +1 @@
1
+ export declare function replacePngXmp(bytes: Uint8Array, xmp: string): Uint8Array;
@@ -0,0 +1,21 @@
1
+ import extractPng from "png-chunks-extract";
2
+ import encodePng from "png-chunks-encode";
3
+ import { joinBytes, startsWithBytes } from "./provenance-bytes.js";
4
+ export function replacePngXmp(bytes, xmp) {
5
+ const chunks = extractPng(bytes);
6
+ const keyword = "XML:com.adobe.xmp\0";
7
+ const kept = chunks.filter((chunk) => !(["iTXt", "tEXt", "zTXt"].includes(chunk.name) &&
8
+ startsWithBytes(chunk.data, keyword)));
9
+ const imageIndex = kept.findIndex((chunk) => chunk.name === "IDAT");
10
+ if (imageIndex < 0)
11
+ throw new Error("PNG has no image data");
12
+ kept.splice(imageIndex, 0, {
13
+ name: "iTXt",
14
+ data: joinBytes([
15
+ new TextEncoder().encode(keyword),
16
+ new Uint8Array(4),
17
+ new TextEncoder().encode(xmp),
18
+ ]),
19
+ });
20
+ return encodePng(kept);
21
+ }
@@ -0,0 +1,2 @@
1
+ /** Whitelist public identity, never copy provider or Fungi private XMP wholesale. */
2
+ export declare function publicProvenancePacket(packet?: string): string;
@@ -0,0 +1,54 @@
1
+ import { DOMParser, XMLSerializer } from "@xmldom/xmldom";
2
+ const fungiNamespace = "https://fungi.computer/ns/provenance/1.0/";
3
+ const iptcNamespace = "http://iptc.org/std/Iptc4xmpExt/2008-02-29/";
4
+ const rdfNamespace = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
5
+ /** Whitelist public identity, never copy provider or Fungi private XMP wholesale. */
6
+ export function publicProvenancePacket(packet) {
7
+ const parser = new DOMParser({
8
+ onError: () => {
9
+ throw new Error("Invalid provenance XML");
10
+ },
11
+ });
12
+ const source = packet
13
+ ? parser.parseFromString(packet, "application/xml")
14
+ : undefined;
15
+ const value = (namespace, name) => {
16
+ const element = source?.getElementsByTagNameNS(namespace, name).item(0);
17
+ if (element?.textContent)
18
+ return element.textContent;
19
+ const descriptions = source?.getElementsByTagNameNS(rdfNamespace, "Description");
20
+ if (!descriptions)
21
+ return undefined;
22
+ for (let index = 0; index < descriptions.length; index++) {
23
+ const attribute = descriptions
24
+ .item(index)
25
+ ?.getAttributeNS(namespace, name);
26
+ if (attribute)
27
+ return attribute;
28
+ }
29
+ return undefined;
30
+ };
31
+ const output = parser.parseFromString('<x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about=""/></rdf:RDF></x:xmpmeta>', "application/xml");
32
+ const description = output
33
+ .getElementsByTagNameNS(rdfNamespace, "Description")
34
+ .item(0);
35
+ if (!description)
36
+ throw new Error("Missing provenance description");
37
+ const fields = [
38
+ { namespace: fungiNamespace, name: "printId", qualified: "fungi:printId" },
39
+ {
40
+ namespace: iptcNamespace,
41
+ name: "DigitalSourceType",
42
+ qualified: "Iptc4xmpExt:DigitalSourceType",
43
+ },
44
+ ];
45
+ for (const { namespace, name, qualified } of fields) {
46
+ const text = value(namespace, name);
47
+ if (text === undefined)
48
+ continue;
49
+ const element = output.createElementNS(namespace, qualified);
50
+ element.textContent = text;
51
+ description.appendChild(element);
52
+ }
53
+ return new XMLSerializer().serializeToString(output);
54
+ }
@@ -0,0 +1,20 @@
1
+ import type { ImageFormat } from "./index.js";
2
+ /** Public file facts only; actor, namespace, billing and reference paths stay private. */
3
+ export type ProvenanceFacts = Readonly<{
4
+ printId: string;
5
+ model: string;
6
+ prompt: string;
7
+ createdAt: number;
8
+ }>;
9
+ export type ProvenanceEmbedResult = Readonly<{
10
+ bytes: Uint8Array;
11
+ embedded: true;
12
+ }> | Readonly<{
13
+ bytes: Uint8Array;
14
+ embedded: false;
15
+ reason: "unsupported-format" | "signed-image" | "invalid-image";
16
+ }>;
17
+ /** Deterministic enrichment. A failed embed never replaces or loses paid bytes.
18
+ * Conservative C2PA detection preserves signed outputs unchanged rather than
19
+ * claiming their old signatures still cover a modified file. */
20
+ export declare function embedProvenance(bytes: Uint8Array, format: ImageFormat | "mp4", facts: ProvenanceFacts): Promise<ProvenanceEmbedResult>;
@@ -0,0 +1,58 @@
1
+ import { Buffer as NodeBuffer } from "node:buffer";
2
+ import { replacePngXmp } from "./provenance-png.js";
3
+ import { replaceJpegXmp } from "./provenance-jpeg.js";
4
+ import { reportCatchFailure } from "./failure.js";
5
+ // The XMP builder and WebP muxer load on the first embed: a Worker that hosts
6
+ // this module never pays to evaluate them at startup.
7
+ async function packet(facts) {
8
+ const { create } = await import("xmlbuilder2");
9
+ const document = create({ invalidCharReplacement: "�" });
10
+ const description = document
11
+ .ele("adobe:ns:meta/", "x:xmpmeta")
12
+ .ele("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf:RDF")
13
+ .ele("rdf:Description")
14
+ .att("rdf:about", "")
15
+ .att("xmlns:xmp", "http://ns.adobe.com/xap/1.0/")
16
+ .att("xmlns:Iptc4xmpExt", "http://iptc.org/std/Iptc4xmpExt/2008-02-29/")
17
+ .att("xmlns:fungi", "https://fungi.computer/ns/provenance/1.0/");
18
+ description
19
+ .ele("xmp:CreateDate")
20
+ .txt(new Date(facts.createdAt).toISOString());
21
+ description
22
+ .ele("Iptc4xmpExt:DigitalSourceType")
23
+ .txt("http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia");
24
+ description.ele("fungi:printId").txt(facts.printId);
25
+ description.ele("fungi:model").txt(facts.model);
26
+ description.ele("fungi:prompt").txt(facts.prompt);
27
+ return document.end({ headless: true });
28
+ }
29
+ async function embedWebp(bytes, xmp) {
30
+ const { default: webp } = await import("node-webpmux");
31
+ const image = new webp.Image();
32
+ await image.load(NodeBuffer.from(bytes));
33
+ image.xmp = NodeBuffer.from(xmp, "utf8");
34
+ return image.save(null);
35
+ }
36
+ /** Deterministic enrichment. A failed embed never replaces or loses paid bytes.
37
+ * Conservative C2PA detection preserves signed outputs unchanged rather than
38
+ * claiming their old signatures still cover a modified file. */
39
+ export async function embedProvenance(bytes, format, facts) {
40
+ if (format === "gif" || format === "mp4")
41
+ return { bytes, embedded: false, reason: "unsupported-format" };
42
+ if (NodeBuffer.from(bytes).includes("c2pa") ||
43
+ NodeBuffer.from(bytes).includes("caBX"))
44
+ return { bytes, embedded: false, reason: "signed-image" };
45
+ try {
46
+ const xmp = await packet(facts);
47
+ const output = format === "png"
48
+ ? replacePngXmp(bytes, xmp)
49
+ : format === "jpeg"
50
+ ? replaceJpegXmp(bytes, xmp)
51
+ : await embedWebp(bytes, xmp);
52
+ return { bytes: output, embedded: true };
53
+ }
54
+ catch (cause) {
55
+ reportCatchFailure("embed-provenance", cause);
56
+ return { bytes, embedded: false, reason: "invalid-image" };
57
+ }
58
+ }
@@ -0,0 +1,158 @@
1
+ export { mediaPendingSchema, mediaBindingCallSchema, mediaBindingGenerationSchema, mediaImageBindingInputSchema, mediaVideoBindingInputSchema, parseMediaBindingCall, mediaBindingRequest, readMediaBindingReply, type MediaBindingCall, type MediaBindingGeneration, type MediaBindingRequest, } from "./media-binding.js";
2
+ import { Hono } from "hono";
3
+ import { z } from "zod";
4
+ import { validateMediaGenerationSettings, type MediaProvider, type MediaReference, type MediaReferenceBytes } from "./index.js";
5
+ import { type MediaExecution, type MediaMetering } from "./media-operation-contract.js";
6
+ import { MediaExecutionError, type MediaBillingRefusalCode } from "./execution-error.js";
7
+ export { MediaBillingRefusal, MediaExecutionError, type MediaBillingRefusalCode, type MediaFailureStage, } from "./execution-error.js";
8
+ export { mediaUnits, type MediaUnits } from "./gateway-models.js";
9
+ export type { MediaMetering, MediaOperation, } from "./media-operation-contract.js";
10
+ export { MediaOperationOwner, mediaOperationDestinationSchema, mediaOperationCoordinatesSchema, mediaOperationStateSchema, mediaImageSchema, mediaOperationResultSchema, } from "./media-operation-owner.js";
11
+ export type { MediaOperationCoordinates, MediaOperationRequest, MediaOperationProjection, MediaOperationDecision, MediaOperationState, MediaOperationResult, MediaOperationEnvironment, MediaOperationHooks, MediaImage, MediaErasurePreflight, MediaErasureReadback, MediaRetentionResult, } from "./media-operation-owner.js";
12
+ export type { MediaOperationContext, MediaOperationStorage, MediaOperationBucket, MediaOperationObject, } from "./media-operation-storage.js";
13
+ /** Decode relative service generation input. Identity comes from the host. */
14
+ export declare const mediaRequestSchema: z.ZodObject<{
15
+ path: z.ZodString;
16
+ settings: z.ZodObject<{
17
+ model: z.ZodString;
18
+ prompt: z.ZodString;
19
+ width: z.ZodNumber;
20
+ height: z.ZodNumber;
21
+ steps: z.ZodOptional<z.ZodNumber>;
22
+ durationSeconds: z.ZodOptional<z.ZodNumber>;
23
+ resolution: z.ZodOptional<z.ZodEnum<{
24
+ readonly p480: "480p";
25
+ readonly p720: "720p";
26
+ readonly p768: "768p";
27
+ readonly p1080: "1080p";
28
+ readonly p4k: "4k";
29
+ }>>;
30
+ aspectRatio: z.ZodOptional<z.ZodEnum<{
31
+ readonly wide: "16:9";
32
+ readonly landscape: "4:3";
33
+ readonly square: "1:1";
34
+ readonly portrait: "3:4";
35
+ readonly tall: "9:16";
36
+ readonly ultrawide: "21:9";
37
+ readonly ultratall: "9:21";
38
+ }>>;
39
+ generateAudio: z.ZodOptional<z.ZodBoolean>;
40
+ references: z.ZodOptional<z.ZodArray<z.ZodObject<{
41
+ computerId: z.ZodString;
42
+ path: z.ZodString;
43
+ role: z.ZodOptional<z.ZodEnum<{
44
+ readonly first: "first-frame";
45
+ readonly last: "last-frame";
46
+ readonly reference: "reference";
47
+ }>>;
48
+ }, z.core.$strict>>>;
49
+ }, z.core.$strict>;
50
+ outputFormat: z.ZodOptional<z.ZodEnum<{
51
+ readonly png: "png";
52
+ readonly jpeg: "jpeg";
53
+ readonly webp: "webp";
54
+ }>>;
55
+ }, z.core.$strict>;
56
+ export { mediaResultSchema } from "./media-operation-contract.js";
57
+ /** A background (video) generation in progress; retry the same request id. */
58
+ /** Identity supplied by the admitting host, never selected by a request body. */
59
+ export type MediaScope = Readonly<{
60
+ /** Host-admitted actor identity. */
61
+ actorId: string;
62
+ /** Host-owned isolation namespace. */
63
+ namespace: string;
64
+ /** Host-owned application or actor within the namespace. */
65
+ owner: string;
66
+ /** Host-admitted destination identity. */
67
+ destinationId: string;
68
+ }>;
69
+ /** Customer-visible refusal text owned by a metered host. */
70
+ export type MediaRefusalMessages = Readonly<Record<MediaBillingRefusalCode, string>>;
71
+ /** Host authority, durable operations, provider, metering and destination access. */
72
+ export type MediaServiceHost = Readonly<{
73
+ /** Identity admitted by the host before service construction. */
74
+ scope: MediaScope;
75
+ /** Resolve durable operations by their service-selected name. */
76
+ operations: {
77
+ /** Resolve the service-selected operation name to its local or RPC owner. */
78
+ getByName(name: string): MediaExecution["operation"];
79
+ };
80
+ /** The provider call for every generation (Workers AI, or the Hub's edge on celld). */
81
+ provider: MediaProvider;
82
+ /** Host-owned address roster, committed before the operation can create storage. */
83
+ beforeAdmit?: ((requestId: string) => Promise<void>) | undefined;
84
+ /** Recheck the host's generation authority only before a new dispatch.
85
+ * Retained output still uses destination authorization when saving. */
86
+ authorizeGeneration?: () => Promise<void>;
87
+ /** Authorize and save generated bytes, and optionally read references. */
88
+ destination: Readonly<{
89
+ /** Check host permission before writing or dispatching generation. */
90
+ authorize: (path: string) => Promise<void>;
91
+ /** Save exact generated bytes to the admitted destination. */
92
+ write: (path: string, bytes: Uint8Array) => Promise<void>;
93
+ /** Authorize and decode one selected reference image. */
94
+ readReference?: (reference: MediaReference) => Promise<MediaReferenceBytes>;
95
+ }>;
96
+ /** Translate host authority failures; undefined uses the shared Media failure. */
97
+ onFailure?: (error: MediaExecutionError) => Response | undefined;
98
+ }> & (Readonly<{
99
+ /** The host pays its own provider directly. */
100
+ metering: "unmetered";
101
+ /** Unmetered hosts do not have billing refusal policy. */
102
+ refusalMessages?: never;
103
+ }> | Readonly<{
104
+ /** Idempotent admission and settlement owned by the host. */
105
+ metering: MediaMetering;
106
+ /** Customer copy for each definitive billing refusal. */
107
+ refusalMessages: MediaRefusalMessages;
108
+ }>);
109
+ /** One Media generation request, already carrying its idempotency key. */
110
+ export type MediaGenerationRequest = Readonly<{
111
+ /** Stable request identity reused for retries. */
112
+ requestId: string;
113
+ /** Fixed destination path for this operation. */
114
+ path: string;
115
+ /** Model settings validated before admission. */
116
+ settings: Parameters<typeof validateMediaGenerationSettings>[0];
117
+ /** Optional verified image format pin. */
118
+ outputFormat?: z.output<typeof mediaRequestSchema>["outputFormat"];
119
+ }>;
120
+ /** The typed result of one generation, shared by the Hono route and the Agent path. */
121
+ export type MediaGenerationOutcome = Readonly<{
122
+ /** Outcome or model-kind discriminant. */
123
+ kind: "succeeded";
124
+ /** Fixed destination or reference file path. */
125
+ path: string;
126
+ /** Output byte length. */
127
+ bytes: number;
128
+ /** Admitted price in milliunits, where 1 cent is 1,000 milliunits. */
129
+ costMilliunits: number;
130
+ /** Actual output encoding. */
131
+ format: "png" | "jpeg" | "gif" | "webp" | "mp4";
132
+ /** Requested image height or nominal video selector, not decoded MP4 dimensions. */
133
+ height: number;
134
+ /** Provider catalog model identifier. */
135
+ model: string;
136
+ /** Requested image width or nominal video selector, not decoded MP4 dimensions. */
137
+ width: number;
138
+ }> | Readonly<{
139
+ /** Outcome or model-kind discriminant. */
140
+ kind: "pending";
141
+ /** Whether generation is running through the durable alarm. */
142
+ background: boolean;
143
+ /** Delay before retrying the same request, in seconds. */
144
+ retryAfterSeconds: number;
145
+ }> | Readonly<{
146
+ /** Outcome or model-kind discriminant. */
147
+ kind: "conflict";
148
+ }> | Readonly<{
149
+ /** Outcome or model-kind discriminant. */
150
+ kind: "interrupted";
151
+ }> | Readonly<{
152
+ /** Outcome or model-kind discriminant. */
153
+ kind: "unavailable";
154
+ }>;
155
+ /** Generate through the Effect core with a Promise boundary preserving typed domain failures. */
156
+ export declare function generateMedia(host: MediaServiceHost, request: MediaGenerationRequest, signal?: AbortSignal): Promise<MediaGenerationOutcome>;
157
+ /** Relative HTTP routes; the caller admits identity and supplies destination access. */
158
+ export declare function createMediaService(host: MediaServiceHost): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;