@codeworksh/harness 0.2.0-dev.20261013121551 → 0.2.0-dev.20261013124604

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.
@@ -1,263 +0,0 @@
1
- import { Effect, Schema } from "effect";
2
- import { Worker } from "node:worker_threads";
3
- //#region src/tool/image/exif.ts
4
- /**
5
- * EXIF orientation for JPEG and WebP. Photon decodes pixels as stored, so a phone
6
- * photo taken sideways would reach the model sideways without this.
7
- */
8
- const readOrientationFromTiff = (bytes, tiffStart) => {
9
- if (tiffStart + 8 > bytes.length) return 1;
10
- const le = (byte(bytes, tiffStart) << 8 | byte(bytes, tiffStart + 1)) === 18761;
11
- const read16 = (pos) => le ? byte(bytes, pos) | byte(bytes, pos + 1) << 8 : byte(bytes, pos) << 8 | byte(bytes, pos + 1);
12
- const read32 = (pos) => le ? (byte(bytes, pos) | byte(bytes, pos + 1) << 8 | byte(bytes, pos + 2) << 16 | byte(bytes, pos + 3) << 24) >>> 0 : (byte(bytes, pos) << 24 | byte(bytes, pos + 1) << 16 | byte(bytes, pos + 2) << 8 | byte(bytes, pos + 3)) >>> 0;
13
- const ifdStart = tiffStart + read32(tiffStart + 4);
14
- if (ifdStart + 2 > bytes.length) return 1;
15
- const entryCount = read16(ifdStart);
16
- for (let index = 0; index < entryCount; index++) {
17
- const entryPos = ifdStart + 2 + index * 12;
18
- if (entryPos + 12 > bytes.length) return 1;
19
- if (read16(entryPos) === 274) {
20
- const value = read16(entryPos + 8);
21
- return value >= 1 && value <= 8 ? value : 1;
22
- }
23
- }
24
- return 1;
25
- };
26
- const findJpegTiffOffset = (bytes) => {
27
- let offset = 2;
28
- while (offset < bytes.length - 1) {
29
- if (bytes[offset] !== 255) return -1;
30
- const marker = bytes[offset + 1];
31
- if (marker === 255) {
32
- offset++;
33
- continue;
34
- }
35
- if (marker === 225) {
36
- const segmentStart = offset + 4;
37
- if (segmentStart + 6 > bytes.length) return -1;
38
- if (hasExifHeader(bytes, segmentStart)) return segmentStart + 6;
39
- }
40
- if (offset + 4 > bytes.length) return -1;
41
- offset += 2 + (byte(bytes, offset + 2) << 8 | byte(bytes, offset + 3));
42
- }
43
- return -1;
44
- };
45
- const findWebpTiffOffset = (bytes) => {
46
- let offset = 12;
47
- while (offset + 8 <= bytes.length) {
48
- const chunkId = String.fromCharCode(...bytes.subarray(offset, offset + 4));
49
- const chunkSize = byte(bytes, offset + 4) | byte(bytes, offset + 5) << 8 | byte(bytes, offset + 6) << 16 | byte(bytes, offset + 7) << 24;
50
- const dataStart = offset + 8;
51
- if (chunkId === "EXIF") {
52
- if (dataStart + chunkSize > bytes.length) return -1;
53
- return chunkSize >= 6 && hasExifHeader(bytes, dataStart) ? dataStart + 6 : dataStart;
54
- }
55
- offset = dataStart + chunkSize + chunkSize % 2;
56
- }
57
- return -1;
58
- };
59
- const hasExifHeader = (bytes, offset) => [
60
- 69,
61
- 120,
62
- 105,
63
- 102,
64
- 0,
65
- 0
66
- ].every((value, index) => bytes[offset + index] === value);
67
- const orientationOf = (bytes) => {
68
- let tiffOffset = -1;
69
- if (bytes[0] === 255 && bytes[1] === 216) tiffOffset = findJpegTiffOffset(bytes);
70
- else if (String.fromCharCode(...bytes.subarray(0, 4)) === "RIFF" && String.fromCharCode(...bytes.subarray(8, 12)) === "WEBP") tiffOffset = findWebpTiffOffset(bytes);
71
- return tiffOffset === -1 ? 1 : readOrientationFromTiff(bytes, tiffOffset);
72
- };
73
- const byte = (bytes, index) => bytes[index] ?? 0;
74
- const rotate90 = (photon, image, dstIndex) => {
75
- const w = image.get_width();
76
- const h = image.get_height();
77
- const src = image.get_raw_pixels();
78
- const dst = new Uint8Array(src.length);
79
- for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) dst.set(src.subarray((y * w + x) * 4, (y * w + x) * 4 + 4), dstIndex(x, y, w, h) * 4);
80
- return new photon.PhotonImage(dst, h, w);
81
- };
82
- const clockwise = (x, y, _w, h) => x * h + (h - 1 - y);
83
- const counterClockwise = (x, y, w, h) => (w - 1 - x) * h + y;
84
- /** Flips mutate `image`; rotations return a new image, which the caller frees alongside the old one. */
85
- const applyOrientation = (photon, image, originalBytes) => {
86
- switch (orientationOf(originalBytes)) {
87
- case 2:
88
- photon.fliph(image);
89
- return image;
90
- case 3:
91
- photon.fliph(image);
92
- photon.flipv(image);
93
- return image;
94
- case 4:
95
- photon.flipv(image);
96
- return image;
97
- case 5: {
98
- const rotated = rotate90(photon, image, clockwise);
99
- photon.fliph(rotated);
100
- return rotated;
101
- }
102
- case 6: return rotate90(photon, image, clockwise);
103
- case 7: {
104
- const rotated = rotate90(photon, image, counterClockwise);
105
- photon.fliph(rotated);
106
- return rotated;
107
- }
108
- case 8: return rotate90(photon, image, counterClockwise);
109
- default: return image;
110
- }
111
- };
112
- //#endregion
113
- //#region src/tool/image/photon.ts
114
- let loading;
115
- /** Photon, loaded once on first use; `undefined` when its WASM cannot load, so images degrade instead of failing reads. */
116
- const load = Effect.promise(() => {
117
- loading ??= import("@silvia-odwyer/photon-node").then((module) => module, () => void 0);
118
- return loading;
119
- });
120
- /** Decode with EXIF orientation applied. The caller frees the result. */
121
- const decode = (photon, bytes) => {
122
- const raw = photon.PhotonImage.new_from_byteslice(bytes);
123
- const oriented = applyOrientation(photon, raw, bytes);
124
- if (oriented !== raw) raw.free();
125
- return oriented;
126
- };
127
- //#endregion
128
- //#region src/tool/image/resize.ts
129
- const defaults = {
130
- maxWidth: 2e3,
131
- maxHeight: 2e3,
132
- maxBytes: 4718592,
133
- jpegQuality: 80
134
- };
135
- /** Tags worker replies: Node posts its own messages on the worker channel too (e.g. under `--watch`). */
136
- const REPLY = "codework:photon-image-resize";
137
- const base64 = (bytes) => {
138
- const data = Buffer.from(bytes).toString("base64");
139
- return {
140
- data,
141
- size: data.length
142
- };
143
- };
144
- /**
145
- * Fit an image within the dimensions and encoded size, in this thread. `null`
146
- * when Photon is unavailable or nothing fits. Fits by: scaling to the max
147
- * dimensions, keeping the smaller of PNG and JPEG, lowering JPEG quality, then
148
- * shrinking by a quarter at a time down to 1x1.
149
- */
150
- const resizeInProcess = (input, mimeType, options = defaults) => Effect.map(load, (photon) => {
151
- if (photon === void 0) return null;
152
- let image;
153
- try {
154
- image = decode(photon, input);
155
- const originalWidth = image.get_width();
156
- const originalHeight = image.get_height();
157
- const inputSize = Math.ceil(input.byteLength / 3) * 4;
158
- if (originalWidth <= options.maxWidth && originalHeight <= options.maxHeight && inputSize < options.maxBytes) return {
159
- data: Buffer.from(input).toString("base64"),
160
- mimeType,
161
- originalWidth,
162
- originalHeight,
163
- width: originalWidth,
164
- height: originalHeight,
165
- wasResized: false
166
- };
167
- let width = originalWidth;
168
- let height = originalHeight;
169
- if (width > options.maxWidth) {
170
- height = Math.max(1, Math.round(height * options.maxWidth / width));
171
- width = options.maxWidth;
172
- }
173
- if (height > options.maxHeight) {
174
- width = Math.max(1, Math.round(width * options.maxHeight / height));
175
- height = options.maxHeight;
176
- }
177
- const qualities = [.../* @__PURE__ */ new Set([
178
- options.jpegQuality,
179
- 85,
180
- 70,
181
- 55,
182
- 40
183
- ])];
184
- while (true) {
185
- const resized = photon.resize(image, width, height, photon.SamplingFilter.Lanczos3);
186
- try {
187
- const fit = [{
188
- ...base64(resized.get_bytes()),
189
- mimeType: "image/png"
190
- }, ...qualities.map((quality) => ({
191
- ...base64(resized.get_bytes_jpeg(quality)),
192
- mimeType: "image/jpeg"
193
- }))].find((candidate) => candidate.size < options.maxBytes);
194
- if (fit !== void 0) return {
195
- data: fit.data,
196
- mimeType: fit.mimeType,
197
- originalWidth,
198
- originalHeight,
199
- width,
200
- height,
201
- wasResized: true
202
- };
203
- } finally {
204
- resized.free();
205
- }
206
- const nextWidth = Math.max(1, Math.floor(width * .75));
207
- const nextHeight = Math.max(1, Math.floor(height * .75));
208
- if (nextWidth === width && nextHeight === height) return null;
209
- width = nextWidth;
210
- height = nextHeight;
211
- }
212
- } catch {
213
- return null;
214
- } finally {
215
- image?.free();
216
- }
217
- });
218
- const workerUrl = () => import.meta.url.endsWith(".ts") ? new URL("./worker.ts", import.meta.url) : new URL("./tool/image/worker.mjs", import.meta.url);
219
- /** The worker could not resize; the caller falls back to this thread. */
220
- var WorkerError = class extends Schema.TaggedError()("ImageResizeWorkerError", { message: Schema.String }) {};
221
- const inWorker = (input, mimeType, options) => Effect.callback((resume) => {
222
- let worker;
223
- try {
224
- worker = new Worker(workerUrl());
225
- } catch (cause) {
226
- resume(Effect.fail(new WorkerError({ message: String(cause) })));
227
- return;
228
- }
229
- const done = (effect) => {
230
- resume(effect);
231
- worker.terminate().catch(() => void 0);
232
- };
233
- worker.on("message", (message) => {
234
- const reply = message;
235
- if (reply?.type !== "codework:photon-image-resize") return;
236
- done("error" in reply ? Effect.fail(new WorkerError({ message: reply.error })) : Effect.succeed(reply.result));
237
- });
238
- worker.once("error", (error) => done(Effect.fail(new WorkerError({ message: error.message }))));
239
- worker.once("exit", (code) => done(Effect.fail(new WorkerError({ message: `Image resize worker exited with code ${code}` }))));
240
- const bytes = new Uint8Array(input);
241
- worker.postMessage({
242
- input: bytes,
243
- mimeType,
244
- options
245
- }, [bytes.buffer]);
246
- return Effect.promise(() => worker.terminate());
247
- });
248
- /**
249
- * {@link resizeInProcess} on a worker thread, so decoding and encoding a large
250
- * image never blocks the harness's event loop. Falls back to this thread when a
251
- * worker cannot start, so a read still works.
252
- */
253
- const resize = (input, mimeType, options = defaults) => inWorker(input, mimeType, options).pipe(Effect.catch(() => resizeInProcess(input, mimeType, options)));
254
- /** Tells the model how to map coordinates on a resized image back to the original. */
255
- const dimensionNote = (result) => {
256
- if (!result.wasResized) return void 0;
257
- const scale = result.originalWidth / result.width;
258
- return `[Image: original ${result.originalWidth}x${result.originalHeight}, displayed at ${result.width}x${result.height}. Multiply coordinates by ${scale.toFixed(2)} to map to original image.]`;
259
- };
260
- //#endregion
261
- export { decode as a, resizeInProcess as i, dimensionNote as n, load as o, resize as r, REPLY as t };
262
-
263
- //# sourceMappingURL=resize-Dwv5dCgY.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"resize-Dwv5dCgY.mjs","names":[],"sources":["../../src/tool/image/exif.ts","../../src/tool/image/photon.ts","../../src/tool/image/resize.ts"],"sourcesContent":["import type { Photon, PhotonImage } from \"./photon.ts\";\n\n/**\n * EXIF orientation for JPEG and WebP. Photon decodes pixels as stored, so a phone\n * photo taken sideways would reach the model sideways without this.\n */\n\nconst readOrientationFromTiff = (bytes: Uint8Array, tiffStart: number): number => {\n\tif (tiffStart + 8 > bytes.length) return 1;\n\tconst le = ((byte(bytes, tiffStart) << 8) | byte(bytes, tiffStart + 1)) === 0x4949;\n\tconst read16 = (pos: number): number =>\n\t\tle ? byte(bytes, pos) | (byte(bytes, pos + 1) << 8) : (byte(bytes, pos) << 8) | byte(bytes, pos + 1);\n\tconst read32 = (pos: number): number =>\n\t\tle\n\t\t\t? (byte(bytes, pos) |\n\t\t\t\t\t(byte(bytes, pos + 1) << 8) |\n\t\t\t\t\t(byte(bytes, pos + 2) << 16) |\n\t\t\t\t\t(byte(bytes, pos + 3) << 24)) >>>\n\t\t\t\t0\n\t\t\t: ((byte(bytes, pos) << 24) |\n\t\t\t\t\t(byte(bytes, pos + 1) << 16) |\n\t\t\t\t\t(byte(bytes, pos + 2) << 8) |\n\t\t\t\t\tbyte(bytes, pos + 3)) >>>\n\t\t\t\t0;\n\n\tconst ifdStart = tiffStart + read32(tiffStart + 4);\n\tif (ifdStart + 2 > bytes.length) return 1;\n\tconst entryCount = read16(ifdStart);\n\tfor (let index = 0; index < entryCount; index++) {\n\t\tconst entryPos = ifdStart + 2 + index * 12;\n\t\tif (entryPos + 12 > bytes.length) return 1;\n\t\tif (read16(entryPos) === 0x0112) {\n\t\t\tconst value = read16(entryPos + 8);\n\t\t\treturn value >= 1 && value <= 8 ? value : 1;\n\t\t}\n\t}\n\treturn 1;\n};\n\nconst findJpegTiffOffset = (bytes: Uint8Array): number => {\n\tlet offset = 2;\n\twhile (offset < bytes.length - 1) {\n\t\tif (bytes[offset] !== 0xff) return -1;\n\t\tconst marker = bytes[offset + 1];\n\t\tif (marker === 0xff) {\n\t\t\toffset++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (marker === 0xe1) {\n\t\t\tconst segmentStart = offset + 4;\n\t\t\tif (segmentStart + 6 > bytes.length) return -1;\n\t\t\tif (hasExifHeader(bytes, segmentStart)) return segmentStart + 6;\n\t\t}\n\t\tif (offset + 4 > bytes.length) return -1;\n\t\toffset += 2 + ((byte(bytes, offset + 2) << 8) | byte(bytes, offset + 3));\n\t}\n\treturn -1;\n};\n\nconst findWebpTiffOffset = (bytes: Uint8Array): number => {\n\tlet offset = 12;\n\twhile (offset + 8 <= bytes.length) {\n\t\tconst chunkId = String.fromCharCode(...bytes.subarray(offset, offset + 4));\n\t\tconst chunkSize =\n\t\t\tbyte(bytes, offset + 4) |\n\t\t\t(byte(bytes, offset + 5) << 8) |\n\t\t\t(byte(bytes, offset + 6) << 16) |\n\t\t\t(byte(bytes, offset + 7) << 24);\n\t\tconst dataStart = offset + 8;\n\t\tif (chunkId === \"EXIF\") {\n\t\t\tif (dataStart + chunkSize > bytes.length) return -1;\n\t\t\t// Some WebP files carry an \"Exif\\0\\0\" prefix before the TIFF header.\n\t\t\treturn chunkSize >= 6 && hasExifHeader(bytes, dataStart) ? dataStart + 6 : dataStart;\n\t\t}\n\t\t// RIFF chunks are padded to an even size.\n\t\toffset = dataStart + chunkSize + (chunkSize % 2);\n\t}\n\treturn -1;\n};\n\nconst hasExifHeader = (bytes: Uint8Array, offset: number): boolean =>\n\t[0x45, 0x78, 0x69, 0x66, 0x00, 0x00].every((value, index) => bytes[offset + index] === value);\n\nconst orientationOf = (bytes: Uint8Array): number => {\n\tlet tiffOffset = -1;\n\tif (bytes[0] === 0xff && bytes[1] === 0xd8) tiffOffset = findJpegTiffOffset(bytes);\n\telse if (\n\t\tString.fromCharCode(...bytes.subarray(0, 4)) === \"RIFF\" &&\n\t\tString.fromCharCode(...bytes.subarray(8, 12)) === \"WEBP\"\n\t)\n\t\ttiffOffset = findWebpTiffOffset(bytes);\n\treturn tiffOffset === -1 ? 1 : readOrientationFromTiff(bytes, tiffOffset);\n};\n\nconst byte = (bytes: Uint8Array, index: number): number => bytes[index] ?? 0;\n\ntype DstIndex = (x: number, y: number, w: number, h: number) => number;\n\nconst rotate90 = (photon: Photon, image: PhotonImage, dstIndex: DstIndex): PhotonImage => {\n\tconst w = image.get_width();\n\tconst h = image.get_height();\n\tconst src = image.get_raw_pixels();\n\tconst dst = new Uint8Array(src.length);\n\tfor (let y = 0; y < h; y++) {\n\t\tfor (let x = 0; x < w; x++) {\n\t\t\tdst.set(src.subarray((y * w + x) * 4, (y * w + x) * 4 + 4), dstIndex(x, y, w, h) * 4);\n\t\t}\n\t}\n\treturn new photon.PhotonImage(dst, h, w);\n};\n\nconst clockwise: DstIndex = (x, y, _w, h) => x * h + (h - 1 - y);\nconst counterClockwise: DstIndex = (x, y, w, h) => (w - 1 - x) * h + y;\n\n/** Flips mutate `image`; rotations return a new image, which the caller frees alongside the old one. */\nexport const applyOrientation = (photon: Photon, image: PhotonImage, originalBytes: Uint8Array): PhotonImage => {\n\tswitch (orientationOf(originalBytes)) {\n\t\tcase 2:\n\t\t\tphoton.fliph(image);\n\t\t\treturn image;\n\t\tcase 3:\n\t\t\tphoton.fliph(image);\n\t\t\tphoton.flipv(image);\n\t\t\treturn image;\n\t\tcase 4:\n\t\t\tphoton.flipv(image);\n\t\t\treturn image;\n\t\tcase 5: {\n\t\t\tconst rotated = rotate90(photon, image, clockwise);\n\t\t\tphoton.fliph(rotated);\n\t\t\treturn rotated;\n\t\t}\n\t\tcase 6:\n\t\t\treturn rotate90(photon, image, clockwise);\n\t\tcase 7: {\n\t\t\tconst rotated = rotate90(photon, image, counterClockwise);\n\t\t\tphoton.fliph(rotated);\n\t\t\treturn rotated;\n\t\t}\n\t\tcase 8:\n\t\t\treturn rotate90(photon, image, counterClockwise);\n\t\tdefault:\n\t\t\treturn image;\n\t}\n};\n","import { Effect } from \"effect\";\nimport { applyOrientation } from \"./exif.ts\";\n\nexport type Photon = typeof import(\"@silvia-odwyer/photon-node\");\nexport type PhotonImage = import(\"@silvia-odwyer/photon-node\").PhotonImage;\n\nlet loading: Promise<Photon | undefined> | undefined;\n\n/** Photon, loaded once on first use; `undefined` when its WASM cannot load, so images degrade instead of failing reads. */\nexport const load: Effect.Effect<Photon | undefined> = Effect.promise(() => {\n\tloading ??= import(\"@silvia-odwyer/photon-node\").then(\n\t\t(module) => module,\n\t\t() => undefined,\n\t);\n\treturn loading;\n});\n\n/** Decode with EXIF orientation applied. The caller frees the result. */\nexport const decode = (photon: Photon, bytes: Uint8Array): PhotonImage => {\n\tconst raw = photon.PhotonImage.new_from_byteslice(bytes);\n\tconst oriented = applyOrientation(photon, raw, bytes);\n\tif (oriented !== raw) raw.free();\n\treturn oriented;\n};\n","import { Effect, Schema } from \"effect\";\nimport { Worker } from \"node:worker_threads\";\nimport { decode, load } from \"./photon.ts\";\n\nexport interface ResizeOptions {\n\treadonly maxWidth: number;\n\treadonly maxHeight: number;\n\t/** Most bytes of base64 payload. */\n\treadonly maxBytes: number;\n\treadonly jpegQuality: number;\n}\n\nexport interface Resized {\n\t/** base64 */\n\treadonly data: string;\n\treadonly mimeType: string;\n\treadonly originalWidth: number;\n\treadonly originalHeight: number;\n\treadonly width: number;\n\treadonly height: number;\n\treadonly wasResized: boolean;\n}\n\n// 4.5MB of base64 leaves headroom below Anthropic's 5MB image limit.\nexport const defaults: ResizeOptions = {\n\tmaxWidth: 2000,\n\tmaxHeight: 2000,\n\tmaxBytes: 4.5 * 1024 * 1024,\n\tjpegQuality: 80,\n};\n\n/** Tags worker replies: Node posts its own messages on the worker channel too (e.g. under `--watch`). */\nexport const REPLY = \"codework:photon-image-resize\";\n\nexport type Reply = { type: typeof REPLY; result: Resized | null } | { type: typeof REPLY; error: string };\n\nconst base64 = (bytes: Uint8Array) => {\n\tconst data = Buffer.from(bytes).toString(\"base64\");\n\treturn { data, size: data.length };\n};\n\n/**\n * Fit an image within the dimensions and encoded size, in this thread. `null`\n * when Photon is unavailable or nothing fits. Fits by: scaling to the max\n * dimensions, keeping the smaller of PNG and JPEG, lowering JPEG quality, then\n * shrinking by a quarter at a time down to 1x1.\n */\nexport const resizeInProcess = (\n\tinput: Uint8Array,\n\tmimeType: string,\n\toptions: ResizeOptions = defaults,\n): Effect.Effect<Resized | null> =>\n\tEffect.map(load, (photon) => {\n\t\tif (photon === undefined) return null;\n\t\tlet image: ReturnType<typeof decode> | undefined;\n\t\ttry {\n\t\t\timage = decode(photon, input);\n\t\t\tconst originalWidth = image.get_width();\n\t\t\tconst originalHeight = image.get_height();\n\t\t\tconst inputSize = Math.ceil(input.byteLength / 3) * 4;\n\t\t\tif (originalWidth <= options.maxWidth && originalHeight <= options.maxHeight && inputSize < options.maxBytes) {\n\t\t\t\treturn {\n\t\t\t\t\tdata: Buffer.from(input).toString(\"base64\"),\n\t\t\t\t\tmimeType,\n\t\t\t\t\toriginalWidth,\n\t\t\t\t\toriginalHeight,\n\t\t\t\t\twidth: originalWidth,\n\t\t\t\t\theight: originalHeight,\n\t\t\t\t\twasResized: false,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// A side never scales below one pixel, or a thin image would vanish.\n\t\t\tlet width = originalWidth;\n\t\t\tlet height = originalHeight;\n\t\t\tif (width > options.maxWidth) {\n\t\t\t\theight = Math.max(1, Math.round((height * options.maxWidth) / width));\n\t\t\t\twidth = options.maxWidth;\n\t\t\t}\n\t\t\tif (height > options.maxHeight) {\n\t\t\t\twidth = Math.max(1, Math.round((width * options.maxHeight) / height));\n\t\t\t\theight = options.maxHeight;\n\t\t\t}\n\n\t\t\tconst qualities = [...new Set([options.jpegQuality, 85, 70, 55, 40])];\n\t\t\twhile (true) {\n\t\t\t\tconst resized = photon.resize(image, width, height, photon.SamplingFilter.Lanczos3);\n\t\t\t\ttry {\n\t\t\t\t\tconst candidates = [\n\t\t\t\t\t\t{ ...base64(resized.get_bytes()), mimeType: \"image/png\" },\n\t\t\t\t\t\t...qualities.map((quality) => ({\n\t\t\t\t\t\t\t...base64(resized.get_bytes_jpeg(quality)),\n\t\t\t\t\t\t\tmimeType: \"image/jpeg\",\n\t\t\t\t\t\t})),\n\t\t\t\t\t];\n\t\t\t\t\tconst fit = candidates.find((candidate) => candidate.size < options.maxBytes);\n\t\t\t\t\tif (fit !== undefined) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tdata: fit.data,\n\t\t\t\t\t\t\tmimeType: fit.mimeType,\n\t\t\t\t\t\t\toriginalWidth,\n\t\t\t\t\t\t\toriginalHeight,\n\t\t\t\t\t\t\twidth,\n\t\t\t\t\t\t\theight,\n\t\t\t\t\t\t\twasResized: true,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t} finally {\n\t\t\t\t\tresized.free();\n\t\t\t\t}\n\t\t\t\tconst nextWidth = Math.max(1, Math.floor(width * 0.75));\n\t\t\t\tconst nextHeight = Math.max(1, Math.floor(height * 0.75));\n\t\t\t\tif (nextWidth === width && nextHeight === height) return null;\n\t\t\t\twidth = nextWidth;\n\t\t\t\theight = nextHeight;\n\t\t\t}\n\t\t} catch {\n\t\t\treturn null;\n\t\t} finally {\n\t\t\timage?.free();\n\t\t}\n\t});\n\n// From source the worker sits beside this module; packed, this module lands in a\n// chunk at the package's output root while the worker keeps its own entry path.\nconst workerUrl = (): URL =>\n\timport.meta.url.endsWith(\".ts\")\n\t\t? new URL(\"./worker.ts\", import.meta.url)\n\t\t: new URL(\"./tool/image/worker.mjs\", import.meta.url);\n\n/** The worker could not resize; the caller falls back to this thread. */\nclass WorkerError extends Schema.TaggedError<WorkerError>()(\"ImageResizeWorkerError\", { message: Schema.String }) {}\n\nconst inWorker = (input: Uint8Array, mimeType: string, options: ResizeOptions) =>\n\tEffect.callback<Resized | null, WorkerError>((resume) => {\n\t\tlet worker: Worker;\n\t\ttry {\n\t\t\tworker = new Worker(workerUrl());\n\t\t} catch (cause) {\n\t\t\t// e.g. a Node permission model without worker access: resize in this thread instead.\n\t\t\tresume(Effect.fail(new WorkerError({ message: String(cause) })));\n\t\t\treturn;\n\t\t}\n\t\tconst done = (effect: Effect.Effect<Resized | null, WorkerError>) => {\n\t\t\tresume(effect);\n\t\t\tvoid worker.terminate().catch(() => undefined);\n\t\t};\n\t\tworker.on(\"message\", (message: unknown) => {\n\t\t\tconst reply = message as Partial<Reply> | null;\n\t\t\tif (reply?.type !== REPLY) return;\n\t\t\tdone(\n\t\t\t\t\"error\" in reply\n\t\t\t\t\t? Effect.fail(new WorkerError({ message: reply.error }))\n\t\t\t\t\t: Effect.succeed((reply as { result: Resized | null }).result),\n\t\t\t);\n\t\t});\n\t\tworker.once(\"error\", (error: Error) => done(Effect.fail(new WorkerError({ message: error.message }))));\n\t\tworker.once(\"exit\", (code) =>\n\t\t\tdone(Effect.fail(new WorkerError({ message: `Image resize worker exited with code ${code}` }))),\n\t\t);\n\t\t// Transferring detaches the buffer, so hand over a copy and leave the caller's bytes intact.\n\t\tconst bytes = new Uint8Array(input);\n\t\tworker.postMessage({ input: bytes, mimeType, options }, [bytes.buffer]);\n\t\treturn Effect.promise(() => worker.terminate());\n\t});\n\n/**\n * {@link resizeInProcess} on a worker thread, so decoding and encoding a large\n * image never blocks the harness's event loop. Falls back to this thread when a\n * worker cannot start, so a read still works.\n */\nexport const resize = (input: Uint8Array, mimeType: string, options: ResizeOptions = defaults) =>\n\tinWorker(input, mimeType, options).pipe(Effect.catch(() => resizeInProcess(input, mimeType, options)));\n\n/** Tells the model how to map coordinates on a resized image back to the original. */\nexport const dimensionNote = (result: Resized): string | undefined => {\n\tif (!result.wasResized) return undefined;\n\tconst scale = result.originalWidth / result.width;\n\treturn `[Image: original ${result.originalWidth}x${result.originalHeight}, displayed at ${result.width}x${result.height}. Multiply coordinates by ${scale.toFixed(2)} to map to original image.]`;\n};\n"],"mappings":"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"}