@standardagents/builder 0.10.1-dev.b8746e9 → 0.10.1-dev.cea2b66

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.
@@ -2,26 +2,30 @@
2
2
  * Server-side image processing for Cloudflare Workers
3
3
  *
4
4
  * Uses @standardagents/sip for memory-efficient processing with scanline-based
5
- * resize and streaming output. Falls back to PNG encoding for transparent images.
5
+ * resize and streaming output. All images are converted to JPEG.
6
6
  *
7
7
  * Features:
8
8
  * - Compress/resize images to fit under 1.5MB target
9
- * - Convert AVIF/WebP/PNG/JPEG to JPEG (default) or PNG (for transparency)
9
+ * - Convert AVIF/WebP/PNG/JPEG to JPEG
10
10
  * - Memory-efficient scanline processing
11
11
  * - Smart quality optimization: quality first, dimensions last
12
12
  */
13
13
  interface ProcessedImage {
14
14
  data: ArrayBuffer;
15
- mimeType: "image/jpeg" | "image/png";
15
+ mimeType: "image/jpeg";
16
16
  width: number;
17
17
  height: number;
18
18
  }
19
19
  /**
20
- * Process an image to ensure it's under 1.5MB and in a supported format.
20
+ * Process an image to ensure it's under 1.5MB and in JPEG format.
21
+ *
22
+ * All images (including transparent PNGs) are converted to JPEG.
23
+ * This trades transparency for reliability - SIP's WASM works correctly
24
+ * in Cloudflare Workers while @jsquash's WASM loading fails.
21
25
  *
22
26
  * @param input - Raw image data as ArrayBuffer
23
- * @param inputMimeType - MIME type hint (used as fallback for format detection)
24
- * @returns Processed image data with updated mimeType and dimensions
27
+ * @param inputMimeType - MIME type hint (unused, kept for API compatibility)
28
+ * @returns Processed image data as JPEG with dimensions
25
29
  */
26
30
  declare function processImage(input: ArrayBuffer, inputMimeType: string): Promise<ProcessedImage>;
27
31
  /**
@@ -1,6 +1,4 @@
1
- import { probe, sip } from '@standardagents/sip';
2
- import { decode as decode$1, encode } from '@jsquash/png';
3
- import { decode } from '@jsquash/avif';
1
+ import { sip } from '@standardagents/sip';
4
2
 
5
3
  // src/image-processing/index.ts
6
4
  var MAX_SIZE = 1.5 * 1024 * 1024;
@@ -10,144 +8,19 @@ async function processImage(input, inputMimeType) {
10
8
  if (input.byteLength > MAX_INPUT_SIZE) {
11
9
  throw new Error(`Image too large: ${input.byteLength} bytes exceeds ${MAX_INPUT_SIZE} byte limit`);
12
10
  }
13
- const probeResult = probe(input);
14
- const format = probeResult.format !== "unknown" ? probeResult.format : detectFormat(input, inputMimeType);
15
- const hasAlpha = probeResult.hasAlpha;
16
- if (hasAlpha && (format === "png" || format === "avif" || format === "webp")) {
17
- return await processPngWithAlpha(input, format);
18
- }
19
- try {
20
- const result = await sip.process(input, {
21
- maxWidth: MAX_DIMENSION,
22
- maxHeight: MAX_DIMENSION,
23
- maxBytes: MAX_SIZE,
24
- quality: 85
25
- });
26
- return {
27
- data: result.data,
28
- mimeType: "image/jpeg",
29
- width: result.width,
30
- height: result.height
31
- };
32
- } catch (err) {
33
- console.error("[sip] Processing failed, falling back:", err);
34
- throw err;
35
- }
36
- }
37
- async function processPngWithAlpha(input, format) {
38
- let imageData;
39
- if (format === "avif") {
40
- imageData = await decode(input);
41
- } else {
42
- imageData = await decode$1(input);
43
- }
44
- const originalWidth = imageData.width;
45
- const originalHeight = imageData.height;
46
- let encoded = await encode(imageData);
47
- if (encoded.byteLength <= MAX_SIZE) {
48
- return {
49
- data: encoded,
50
- mimeType: "image/png",
51
- width: originalWidth,
52
- height: originalHeight
53
- };
54
- }
55
- let scale = 0.9;
56
- while (encoded.byteLength > MAX_SIZE && scale > 0.15) {
57
- const newWidth = Math.floor(originalWidth * scale);
58
- const newHeight = Math.floor(originalHeight * scale);
59
- if (newWidth < 100 || newHeight < 100) {
60
- scale *= 0.9;
61
- continue;
62
- }
63
- const resized = resizeRgba(
64
- new Uint8ClampedArray(imageData.data),
65
- originalWidth,
66
- originalHeight,
67
- newWidth,
68
- newHeight
69
- );
70
- const resizedImageData = { data: resized, width: newWidth, height: newHeight };
71
- encoded = await encode(resizedImageData);
72
- if (encoded.byteLength <= MAX_SIZE) {
73
- return {
74
- data: encoded,
75
- mimeType: "image/png",
76
- width: newWidth,
77
- height: newHeight
78
- };
79
- }
80
- scale *= 0.9;
81
- }
82
- const finalWidth = Math.floor(originalWidth * 0.15);
83
- const finalHeight = Math.floor(originalHeight * 0.15);
84
- const finalResized = resizeRgba(
85
- new Uint8ClampedArray(imageData.data),
86
- originalWidth,
87
- originalHeight,
88
- finalWidth,
89
- finalHeight
90
- );
91
- encoded = await encode({ data: finalResized, width: finalWidth, height: finalHeight });
11
+ const result = await sip.process(input, {
12
+ maxWidth: MAX_DIMENSION,
13
+ maxHeight: MAX_DIMENSION,
14
+ maxBytes: MAX_SIZE,
15
+ quality: 85
16
+ });
92
17
  return {
93
- data: encoded,
94
- mimeType: "image/png",
95
- width: finalWidth,
96
- height: finalHeight
18
+ data: result.data,
19
+ mimeType: "image/jpeg",
20
+ width: result.width,
21
+ height: result.height
97
22
  };
98
23
  }
99
- function resizeRgba(src, srcWidth, srcHeight, dstWidth, dstHeight) {
100
- const dst = new Uint8ClampedArray(dstWidth * dstHeight * 4);
101
- const xScale = srcWidth / dstWidth;
102
- const yScale = srcHeight / dstHeight;
103
- for (let dstY = 0; dstY < dstHeight; dstY++) {
104
- for (let dstX = 0; dstX < dstWidth; dstX++) {
105
- const srcXFloat = dstX * xScale;
106
- const srcYFloat = dstY * yScale;
107
- const srcX0 = Math.floor(srcXFloat);
108
- const srcY0 = Math.floor(srcYFloat);
109
- const srcX1 = Math.min(srcX0 + 1, srcWidth - 1);
110
- const srcY1 = Math.min(srcY0 + 1, srcHeight - 1);
111
- const tx = srcXFloat - srcX0;
112
- const ty = srcYFloat - srcY0;
113
- const idx00 = (srcY0 * srcWidth + srcX0) * 4;
114
- const idx10 = (srcY0 * srcWidth + srcX1) * 4;
115
- const idx01 = (srcY1 * srcWidth + srcX0) * 4;
116
- const idx11 = (srcY1 * srcWidth + srcX1) * 4;
117
- const dstIdx = (dstY * dstWidth + dstX) * 4;
118
- for (let c = 0; c < 4; c++) {
119
- const v00 = src[idx00 + c];
120
- const v10 = src[idx10 + c];
121
- const v01 = src[idx01 + c];
122
- const v11 = src[idx11 + c];
123
- const top = v00 * (1 - tx) + v10 * tx;
124
- const bottom = v01 * (1 - tx) + v11 * tx;
125
- dst[dstIdx + c] = Math.round(top * (1 - ty) + bottom * ty);
126
- }
127
- }
128
- }
129
- return dst;
130
- }
131
- function detectFormat(data, mimeType) {
132
- const bytes = new Uint8Array(data.slice(0, 12));
133
- if (bytes[4] === 102 && bytes[5] === 116 && bytes[6] === 121 && bytes[7] === 112) {
134
- const brand = String.fromCharCode(...bytes.slice(8, 12));
135
- if (brand === "avif" || brand === "avis") return "avif";
136
- }
137
- if (bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71) {
138
- return "png";
139
- }
140
- if (bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) {
141
- return "jpeg";
142
- }
143
- if (bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80) {
144
- return "webp";
145
- }
146
- if (mimeType.includes("png")) return "png";
147
- if (mimeType.includes("webp")) return "webp";
148
- if (mimeType.includes("avif")) return "avif";
149
- return "jpeg";
150
- }
151
24
  function needsProcessing(data, mimeType) {
152
25
  const binaryLength = Math.ceil(data.length * 3 / 4);
153
26
  return binaryLength > MAX_SIZE || mimeType.includes("avif") || mimeType.includes("webp");
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/image-processing/index.ts"],"names":["decodeAvif","decodePng","encodePng"],"mappings":";;;;;AA0BA,IAAM,QAAA,GAAW,MAAM,IAAA,GAAO,IAAA;AAG9B,IAAM,aAAA,GAAgB,IAAA;AAGtB,IAAM,cAAA,GAAiB,KAAK,IAAA,GAAO,IAAA;AAgBnC,eAAsB,YAAA,CACpB,OACA,aAAA,EACyB;AAEzB,EAAA,IAAI,KAAA,CAAM,aAAa,cAAA,EAAgB;AACrC,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,iBAAA,EAAoB,MAAM,UAAU,CAAA,eAAA,EAAkB,cAAc,CAAA,WAAA,CAAa,CAAA;AAAA,EACnG;AAGA,EAAA,MAAM,WAAA,GAAc,MAAM,KAAK,CAAA;AAC/B,EAAA,MAAM,MAAA,GAAS,YAAY,MAAA,KAAW,SAAA,GAAY,YAAY,MAAA,GAAS,YAAA,CAAa,OAAO,aAAa,CAAA;AACxG,EAAA,MAAM,WAAW,WAAA,CAAY,QAAA;AAG7B,EAAA,IAAI,aAAa,MAAA,KAAW,KAAA,IAAS,MAAA,KAAW,MAAA,IAAU,WAAW,MAAA,CAAA,EAAS;AAC5E,IAAA,OAAO,MAAM,mBAAA,CAAoB,KAAA,EAAO,MAAM,CAAA;AAAA,EAChD;AAGA,EAAA,IAAI;AACF,IAAA,MAAM,MAAA,GAAS,MAAM,GAAA,CAAI,OAAA,CAAQ,KAAA,EAAO;AAAA,MACtC,QAAA,EAAU,aAAA;AAAA,MACV,SAAA,EAAW,aAAA;AAAA,MACX,QAAA,EAAU,QAAA;AAAA,MACV,OAAA,EAAS;AAAA,KACV,CAAA;AAED,IAAA,OAAO;AAAA,MACL,MAAM,MAAA,CAAO,IAAA;AAAA,MACb,QAAA,EAAU,YAAA;AAAA,MACV,OAAO,MAAA,CAAO,KAAA;AAAA,MACd,QAAQ,MAAA,CAAO;AAAA,KACjB;AAAA,EACF,SAAS,GAAA,EAAK;AAEZ,IAAA,OAAA,CAAQ,KAAA,CAAM,0CAA0C,GAAG,CAAA;AAC3D,IAAA,MAAM,GAAA;AAAA,EACR;AACF;AAKA,eAAe,mBAAA,CACb,OACA,MAAA,EACyB;AAEzB,EAAA,IAAI,SAAA;AAEJ,EAAA,IAAI,WAAW,MAAA,EAAQ;AACrB,IAAA,SAAA,GAAY,MAAMA,OAAW,KAAK,CAAA;AAAA,EACpC,CAAA,MAAO;AACL,IAAA,SAAA,GAAY,MAAMC,SAAU,KAAK,CAAA;AAAA,EACnC;AAEA,EAAA,MAAM,gBAAgB,SAAA,CAAU,KAAA;AAChC,EAAA,MAAM,iBAAiB,SAAA,CAAU,MAAA;AAGjC,EAAA,IAAI,OAAA,GAAU,MAAMC,MAAA,CAAU,SAAS,CAAA;AAEvC,EAAA,IAAI,OAAA,CAAQ,cAAc,QAAA,EAAU;AAClC,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,OAAA;AAAA,MACN,QAAA,EAAU,WAAA;AAAA,MACV,KAAA,EAAO,aAAA;AAAA,MACP,MAAA,EAAQ;AAAA,KACV;AAAA,EACF;AAIA,EAAA,IAAI,KAAA,GAAQ,GAAA;AACZ,EAAA,OAAO,OAAA,CAAQ,UAAA,GAAa,QAAA,IAAY,KAAA,GAAQ,IAAA,EAAM;AACpD,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,KAAA,CAAM,aAAA,GAAgB,KAAK,CAAA;AACjD,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,KAAA,CAAM,cAAA,GAAiB,KAAK,CAAA;AAEnD,IAAA,IAAI,QAAA,GAAW,GAAA,IAAO,SAAA,GAAY,GAAA,EAAK;AACrC,MAAA,KAAA,IAAS,GAAA;AACT,MAAA;AAAA,IACF;AAGA,IAAA,MAAM,OAAA,GAAU,UAAA;AAAA,MACd,IAAI,iBAAA,CAAkB,SAAA,CAAU,IAAI,CAAA;AAAA,MACpC,aAAA;AAAA,MACA,cAAA;AAAA,MACA,QAAA;AAAA,MACA;AAAA,KACF;AAEA,IAAA,MAAM,mBAAkC,EAAE,IAAA,EAAM,SAAS,KAAA,EAAO,QAAA,EAAU,QAAQ,SAAA,EAAU;AAC5F,IAAA,OAAA,GAAU,MAAMA,OAAU,gBAAgB,CAAA;AAE1C,IAAA,IAAI,OAAA,CAAQ,cAAc,QAAA,EAAU;AAClC,MAAA,OAAO;AAAA,QACL,IAAA,EAAM,OAAA;AAAA,QACN,QAAA,EAAU,WAAA;AAAA,QACV,KAAA,EAAO,QAAA;AAAA,QACP,MAAA,EAAQ;AAAA,OACV;AAAA,IACF;AAEA,IAAA,KAAA,IAAS,GAAA;AAAA,EACX;AAGA,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,KAAA,CAAM,aAAA,GAAgB,IAAI,CAAA;AAClD,EAAA,MAAM,WAAA,GAAc,IAAA,CAAK,KAAA,CAAM,cAAA,GAAiB,IAAI,CAAA;AACpD,EAAA,MAAM,YAAA,GAAe,UAAA;AAAA,IACnB,IAAI,iBAAA,CAAkB,SAAA,CAAU,IAAI,CAAA;AAAA,IACpC,aAAA;AAAA,IACA,cAAA;AAAA,IACA,UAAA;AAAA,IACA;AAAA,GACF;AACA,EAAA,OAAA,GAAU,MAAMA,OAAU,EAAE,IAAA,EAAM,cAAc,KAAA,EAAO,UAAA,EAAY,MAAA,EAAQ,WAAA,EAAa,CAAA;AAExF,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,OAAA;AAAA,IACN,QAAA,EAAU,WAAA;AAAA,IACV,KAAA,EAAO,UAAA;AAAA,IACP,MAAA,EAAQ;AAAA,GACV;AACF;AAKA,SAAS,UAAA,CACP,GAAA,EACA,QAAA,EACA,SAAA,EACA,UACA,SAAA,EACmB;AACnB,EAAA,MAAM,GAAA,GAAM,IAAI,iBAAA,CAAkB,QAAA,GAAW,YAAY,CAAC,CAAA;AAC1D,EAAA,MAAM,SAAS,QAAA,GAAW,QAAA;AAC1B,EAAA,MAAM,SAAS,SAAA,GAAY,SAAA;AAE3B,EAAA,KAAA,IAAS,IAAA,GAAO,CAAA,EAAG,IAAA,GAAO,SAAA,EAAW,IAAA,EAAA,EAAQ;AAC3C,IAAA,KAAA,IAAS,IAAA,GAAO,CAAA,EAAG,IAAA,GAAO,QAAA,EAAU,IAAA,EAAA,EAAQ;AAC1C,MAAA,MAAM,YAAY,IAAA,GAAO,MAAA;AACzB,MAAA,MAAM,YAAY,IAAA,GAAO,MAAA;AACzB,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,SAAS,CAAA;AAClC,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,SAAS,CAAA;AAClC,MAAA,MAAM,QAAQ,IAAA,CAAK,GAAA,CAAI,KAAA,GAAQ,CAAA,EAAG,WAAW,CAAC,CAAA;AAC9C,MAAA,MAAM,QAAQ,IAAA,CAAK,GAAA,CAAI,KAAA,GAAQ,CAAA,EAAG,YAAY,CAAC,CAAA;AAC/C,MAAA,MAAM,KAAK,SAAA,GAAY,KAAA;AACvB,MAAA,MAAM,KAAK,SAAA,GAAY,KAAA;AAEvB,MAAA,MAAM,KAAA,GAAA,CAAS,KAAA,GAAQ,QAAA,GAAW,KAAA,IAAS,CAAA;AAC3C,MAAA,MAAM,KAAA,GAAA,CAAS,KAAA,GAAQ,QAAA,GAAW,KAAA,IAAS,CAAA;AAC3C,MAAA,MAAM,KAAA,GAAA,CAAS,KAAA,GAAQ,QAAA,GAAW,KAAA,IAAS,CAAA;AAC3C,MAAA,MAAM,KAAA,GAAA,CAAS,KAAA,GAAQ,QAAA,GAAW,KAAA,IAAS,CAAA;AAC3C,MAAA,MAAM,MAAA,GAAA,CAAU,IAAA,GAAO,QAAA,GAAW,IAAA,IAAQ,CAAA;AAE1C,MAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAA,EAAA,EAAK;AAC1B,QAAA,MAAM,GAAA,GAAM,GAAA,CAAI,KAAA,GAAQ,CAAC,CAAA;AACzB,QAAA,MAAM,GAAA,GAAM,GAAA,CAAI,KAAA,GAAQ,CAAC,CAAA;AACzB,QAAA,MAAM,GAAA,GAAM,GAAA,CAAI,KAAA,GAAQ,CAAC,CAAA;AACzB,QAAA,MAAM,GAAA,GAAM,GAAA,CAAI,KAAA,GAAQ,CAAC,CAAA;AACzB,QAAA,MAAM,GAAA,GAAM,GAAA,IAAO,CAAA,GAAI,EAAA,CAAA,GAAM,GAAA,GAAM,EAAA;AACnC,QAAA,MAAM,MAAA,GAAS,GAAA,IAAO,CAAA,GAAI,EAAA,CAAA,GAAM,GAAA,GAAM,EAAA;AACtC,QAAA,GAAA,CAAI,MAAA,GAAS,CAAC,CAAA,GAAI,IAAA,CAAK,MAAM,GAAA,IAAO,CAAA,GAAI,EAAA,CAAA,GAAM,MAAA,GAAS,EAAE,CAAA;AAAA,MAC3D;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,GAAA;AACT;AAKA,SAAS,YAAA,CAAa,MAAmB,QAAA,EAA0B;AACjE,EAAA,MAAM,QAAQ,IAAI,UAAA,CAAW,KAAK,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA;AAE9C,EAAA,IAAI,KAAA,CAAM,CAAC,CAAA,KAAM,GAAA,IAAQ,MAAM,CAAC,CAAA,KAAM,GAAA,IAAQ,KAAA,CAAM,CAAC,CAAA,KAAM,GAAA,IAAQ,KAAA,CAAM,CAAC,MAAM,GAAA,EAAM;AACpF,IAAA,MAAM,KAAA,GAAQ,OAAO,YAAA,CAAa,GAAG,MAAM,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA;AACvD,IAAA,IAAI,KAAA,KAAU,MAAA,IAAU,KAAA,KAAU,MAAA,EAAQ,OAAO,MAAA;AAAA,EACnD;AAEA,EAAA,IAAI,KAAA,CAAM,CAAC,CAAA,KAAM,GAAA,IAAQ,MAAM,CAAC,CAAA,KAAM,EAAA,IAAQ,KAAA,CAAM,CAAC,CAAA,KAAM,EAAA,IAAQ,KAAA,CAAM,CAAC,MAAM,EAAA,EAAM;AACpF,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,IAAI,KAAA,CAAM,CAAC,CAAA,KAAM,GAAA,IAAQ,KAAA,CAAM,CAAC,CAAA,KAAM,GAAA,IAAQ,KAAA,CAAM,CAAC,CAAA,KAAM,GAAA,EAAM;AAC/D,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,IAAI,KAAA,CAAM,CAAC,CAAA,KAAM,EAAA,IAAQ,KAAA,CAAM,CAAC,CAAA,KAAM,EAAA,IAAQ,KAAA,CAAM,CAAC,CAAA,KAAM,EAAA,IAAQ,MAAM,CAAC,CAAA,KAAM,EAAA,IAC5E,KAAA,CAAM,CAAC,CAAA,KAAM,EAAA,IAAQ,KAAA,CAAM,CAAC,CAAA,KAAM,EAAA,IAAQ,KAAA,CAAM,EAAE,CAAA,KAAM,EAAA,IAAQ,KAAA,CAAM,EAAE,MAAM,EAAA,EAAM;AACtF,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,IAAI,QAAA,CAAS,QAAA,CAAS,KAAK,CAAA,EAAG,OAAO,KAAA;AACrC,EAAA,IAAI,QAAA,CAAS,QAAA,CAAS,MAAM,CAAA,EAAG,OAAO,MAAA;AACtC,EAAA,IAAI,QAAA,CAAS,QAAA,CAAS,MAAM,CAAA,EAAG,OAAO,MAAA;AACtC,EAAA,OAAO,MAAA;AACT;AASO,SAAS,eAAA,CAAgB,MAAc,QAAA,EAA2B;AAEvE,EAAA,MAAM,eAAe,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,MAAA,GAAS,IAAI,CAAC,CAAA;AAGlD,EAAA,OACE,YAAA,GAAe,YACf,QAAA,CAAS,QAAA,CAAS,MAAM,CAAA,IACxB,QAAA,CAAS,SAAS,MAAM,CAAA;AAE5B;AAKO,SAAS,oBAAoB,MAAA,EAA6B;AAC/D,EAAA,MAAM,YAAA,GAAe,KAAK,MAAM,CAAA;AAChC,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,YAAA,CAAa,MAAM,CAAA;AAChD,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,YAAA,CAAa,QAAQ,CAAA,EAAA,EAAK;AAC5C,IAAA,KAAA,CAAM,CAAC,CAAA,GAAI,YAAA,CAAa,UAAA,CAAW,CAAC,CAAA;AAAA,EACtC;AACA,EAAA,OAAO,KAAA,CAAM,MAAA;AACf;AAKO,SAAS,oBAAoB,MAAA,EAA6B;AAC/D,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,MAAM,CAAA;AACnC,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAA,IAAU,MAAA,CAAO,YAAA,CAAa,KAAA,CAAM,CAAC,CAAC,CAAA;AAAA,EACxC;AACA,EAAA,OAAO,KAAK,MAAM,CAAA;AACpB","file":"image-processing.js","sourcesContent":["/**\n * Server-side image processing for Cloudflare Workers\n *\n * Uses @standardagents/sip for memory-efficient processing with scanline-based\n * resize and streaming output. Falls back to PNG encoding for transparent images.\n *\n * Features:\n * - Compress/resize images to fit under 1.5MB target\n * - Convert AVIF/WebP/PNG/JPEG to JPEG (default) or PNG (for transparency)\n * - Memory-efficient scanline processing\n * - Smart quality optimization: quality first, dimensions last\n */\n\nimport { sip, probe } from \"@standardagents/sip\";\nimport { encode as encodePng, decode as decodePng } from \"@jsquash/png\";\nimport { decode as decodeAvif } from \"@jsquash/avif\";\n\n// ImageData type for Workers (matches the structure from @jsquash)\ninterface ImageDataLike {\n data: Uint8ClampedArray;\n width: number;\n height: number;\n}\n\n// Target 1.5MB binary → ~2MB base64 for RPC transfer\n// SQLite limit is 2MB per row, but base64 overhead means we need headroom\nconst MAX_SIZE = 1.5 * 1024 * 1024;\n\n// Memory safety limit\nconst MAX_DIMENSION = 4096;\n\n// Reject images that would consume too much memory before decoding\nconst MAX_INPUT_SIZE = 20 * 1024 * 1024; // 20MB\n\nexport interface ProcessedImage {\n data: ArrayBuffer;\n mimeType: \"image/jpeg\" | \"image/png\";\n width: number;\n height: number;\n}\n\n/**\n * Process an image to ensure it's under 1.5MB and in a supported format.\n *\n * @param input - Raw image data as ArrayBuffer\n * @param inputMimeType - MIME type hint (used as fallback for format detection)\n * @returns Processed image data with updated mimeType and dimensions\n */\nexport async function processImage(\n input: ArrayBuffer,\n inputMimeType: string\n): Promise<ProcessedImage> {\n // Memory safety: reject very large images\n if (input.byteLength > MAX_INPUT_SIZE) {\n throw new Error(`Image too large: ${input.byteLength} bytes exceeds ${MAX_INPUT_SIZE} byte limit`);\n }\n\n // Probe for format and transparency\n const probeResult = probe(input);\n const format = probeResult.format !== 'unknown' ? probeResult.format : detectFormat(input, inputMimeType);\n const hasAlpha = probeResult.hasAlpha;\n\n // For transparent images, we need to preserve alpha with PNG\n if (hasAlpha && (format === 'png' || format === 'avif' || format === 'webp')) {\n return await processPngWithAlpha(input, format);\n }\n\n // Use sip for efficient JPEG processing\n try {\n const result = await sip.process(input, {\n maxWidth: MAX_DIMENSION,\n maxHeight: MAX_DIMENSION,\n maxBytes: MAX_SIZE,\n quality: 85,\n });\n\n return {\n data: result.data,\n mimeType: \"image/jpeg\",\n width: result.width,\n height: result.height,\n };\n } catch (err) {\n // Fallback: if sip fails, try original processing\n console.error('[sip] Processing failed, falling back:', err);\n throw err;\n }\n}\n\n/**\n * Process PNG/AVIF images with transparency preservation\n */\nasync function processPngWithAlpha(\n input: ArrayBuffer,\n format: string\n): Promise<ProcessedImage> {\n // Decode based on format\n let imageData: ImageDataLike;\n\n if (format === 'avif') {\n imageData = await decodeAvif(input);\n } else {\n imageData = await decodePng(input);\n }\n\n const originalWidth = imageData.width;\n const originalHeight = imageData.height;\n\n // First try encoding at original size\n let encoded = await encodePng(imageData);\n\n if (encoded.byteLength <= MAX_SIZE) {\n return {\n data: encoded,\n mimeType: \"image/png\",\n width: originalWidth,\n height: originalHeight,\n };\n }\n\n // PNG too large - resize using sip's scanline method and re-encode\n // We need to resize while preserving alpha, so we'll do a simple RGBA resize\n let scale = 0.9;\n while (encoded.byteLength > MAX_SIZE && scale > 0.15) {\n const newWidth = Math.floor(originalWidth * scale);\n const newHeight = Math.floor(originalHeight * scale);\n\n if (newWidth < 100 || newHeight < 100) {\n scale *= 0.9;\n continue;\n }\n\n // Simple bilinear resize for RGBA\n const resized = resizeRgba(\n new Uint8ClampedArray(imageData.data),\n originalWidth,\n originalHeight,\n newWidth,\n newHeight\n );\n\n const resizedImageData: ImageDataLike = { data: resized, width: newWidth, height: newHeight };\n encoded = await encodePng(resizedImageData);\n\n if (encoded.byteLength <= MAX_SIZE) {\n return {\n data: encoded,\n mimeType: \"image/png\",\n width: newWidth,\n height: newHeight,\n };\n }\n\n scale *= 0.9;\n }\n\n // Fallback: smallest PNG\n const finalWidth = Math.floor(originalWidth * 0.15);\n const finalHeight = Math.floor(originalHeight * 0.15);\n const finalResized = resizeRgba(\n new Uint8ClampedArray(imageData.data),\n originalWidth,\n originalHeight,\n finalWidth,\n finalHeight\n );\n encoded = await encodePng({ data: finalResized, width: finalWidth, height: finalHeight });\n\n return {\n data: encoded,\n mimeType: \"image/png\",\n width: finalWidth,\n height: finalHeight,\n };\n}\n\n/**\n * Simple bilinear RGBA resize\n */\nfunction resizeRgba(\n src: Uint8ClampedArray,\n srcWidth: number,\n srcHeight: number,\n dstWidth: number,\n dstHeight: number\n): Uint8ClampedArray {\n const dst = new Uint8ClampedArray(dstWidth * dstHeight * 4);\n const xScale = srcWidth / dstWidth;\n const yScale = srcHeight / dstHeight;\n\n for (let dstY = 0; dstY < dstHeight; dstY++) {\n for (let dstX = 0; dstX < dstWidth; dstX++) {\n const srcXFloat = dstX * xScale;\n const srcYFloat = dstY * yScale;\n const srcX0 = Math.floor(srcXFloat);\n const srcY0 = Math.floor(srcYFloat);\n const srcX1 = Math.min(srcX0 + 1, srcWidth - 1);\n const srcY1 = Math.min(srcY0 + 1, srcHeight - 1);\n const tx = srcXFloat - srcX0;\n const ty = srcYFloat - srcY0;\n\n const idx00 = (srcY0 * srcWidth + srcX0) * 4;\n const idx10 = (srcY0 * srcWidth + srcX1) * 4;\n const idx01 = (srcY1 * srcWidth + srcX0) * 4;\n const idx11 = (srcY1 * srcWidth + srcX1) * 4;\n const dstIdx = (dstY * dstWidth + dstX) * 4;\n\n for (let c = 0; c < 4; c++) {\n const v00 = src[idx00 + c];\n const v10 = src[idx10 + c];\n const v01 = src[idx01 + c];\n const v11 = src[idx11 + c];\n const top = v00 * (1 - tx) + v10 * tx;\n const bottom = v01 * (1 - tx) + v11 * tx;\n dst[dstIdx + c] = Math.round(top * (1 - ty) + bottom * ty);\n }\n }\n }\n\n return dst;\n}\n\n/**\n * Detect image format from magic bytes (fallback)\n */\nfunction detectFormat(data: ArrayBuffer, mimeType: string): string {\n const bytes = new Uint8Array(data.slice(0, 12));\n\n if (bytes[4] === 0x66 && bytes[5] === 0x74 && bytes[6] === 0x79 && bytes[7] === 0x70) {\n const brand = String.fromCharCode(...bytes.slice(8, 12));\n if (brand === \"avif\" || brand === \"avis\") return \"avif\";\n }\n\n if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4E && bytes[3] === 0x47) {\n return \"png\";\n }\n\n if (bytes[0] === 0xFF && bytes[1] === 0xD8 && bytes[2] === 0xFF) {\n return \"jpeg\";\n }\n\n if (bytes[0] === 0x52 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x46 &&\n bytes[8] === 0x57 && bytes[9] === 0x45 && bytes[10] === 0x42 && bytes[11] === 0x50) {\n return \"webp\";\n }\n\n if (mimeType.includes(\"png\")) return \"png\";\n if (mimeType.includes(\"webp\")) return \"webp\";\n if (mimeType.includes(\"avif\")) return \"avif\";\n return \"jpeg\";\n}\n\n/**\n * Check if an image needs processing based on size and format.\n *\n * @param data - Base64-encoded image data\n * @param mimeType - MIME type of the image\n * @returns true if processing is needed\n */\nexport function needsProcessing(data: string, mimeType: string): boolean {\n // Decode base64 to get actual size\n const binaryLength = Math.ceil(data.length * 3 / 4);\n\n // Process if >2MB or unsupported format\n return (\n binaryLength > MAX_SIZE ||\n mimeType.includes(\"avif\") ||\n mimeType.includes(\"webp\")\n );\n}\n\n/**\n * Convert base64 string to ArrayBuffer\n */\nexport function base64ToArrayBuffer(base64: string): ArrayBuffer {\n const binaryString = atob(base64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n return bytes.buffer;\n}\n\n/**\n * Convert ArrayBuffer to base64 string\n */\nexport function arrayBufferToBase64(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary);\n}\n"]}
1
+ {"version":3,"sources":["../src/image-processing/index.ts"],"names":[],"mappings":";;;AAiBA,IAAM,QAAA,GAAW,MAAM,IAAA,GAAO,IAAA;AAG9B,IAAM,aAAA,GAAgB,IAAA;AAGtB,IAAM,cAAA,GAAiB,KAAK,IAAA,GAAO,IAAA;AAoBnC,eAAsB,YAAA,CACpB,OACA,aAAA,EACyB;AAEzB,EAAA,IAAI,KAAA,CAAM,aAAa,cAAA,EAAgB;AACrC,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,iBAAA,EAAoB,MAAM,UAAU,CAAA,eAAA,EAAkB,cAAc,CAAA,WAAA,CAAa,CAAA;AAAA,EACnG;AAGA,EAAA,MAAM,MAAA,GAAS,MAAM,GAAA,CAAI,OAAA,CAAQ,KAAA,EAAO;AAAA,IACtC,QAAA,EAAU,aAAA;AAAA,IACV,SAAA,EAAW,aAAA;AAAA,IACX,QAAA,EAAU,QAAA;AAAA,IACV,OAAA,EAAS;AAAA,GACV,CAAA;AAED,EAAA,OAAO;AAAA,IACL,MAAM,MAAA,CAAO,IAAA;AAAA,IACb,QAAA,EAAU,YAAA;AAAA,IACV,OAAO,MAAA,CAAO,KAAA;AAAA,IACd,QAAQ,MAAA,CAAO;AAAA,GACjB;AACF;AAuCO,SAAS,eAAA,CAAgB,MAAc,QAAA,EAA2B;AAEvE,EAAA,MAAM,eAAe,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,MAAA,GAAS,IAAI,CAAC,CAAA;AAGlD,EAAA,OACE,YAAA,GAAe,YACf,QAAA,CAAS,QAAA,CAAS,MAAM,CAAA,IACxB,QAAA,CAAS,SAAS,MAAM,CAAA;AAE5B;AAKO,SAAS,oBAAoB,MAAA,EAA6B;AAC/D,EAAA,MAAM,YAAA,GAAe,KAAK,MAAM,CAAA;AAChC,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,YAAA,CAAa,MAAM,CAAA;AAChD,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,YAAA,CAAa,QAAQ,CAAA,EAAA,EAAK;AAC5C,IAAA,KAAA,CAAM,CAAC,CAAA,GAAI,YAAA,CAAa,UAAA,CAAW,CAAC,CAAA;AAAA,EACtC;AACA,EAAA,OAAO,KAAA,CAAM,MAAA;AACf;AAKO,SAAS,oBAAoB,MAAA,EAA6B;AAC/D,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,MAAM,CAAA;AACnC,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAA,IAAU,MAAA,CAAO,YAAA,CAAa,KAAA,CAAM,CAAC,CAAC,CAAA;AAAA,EACxC;AACA,EAAA,OAAO,KAAK,MAAM,CAAA;AACpB","file":"image-processing.js","sourcesContent":["/**\n * Server-side image processing for Cloudflare Workers\n *\n * Uses @standardagents/sip for memory-efficient processing with scanline-based\n * resize and streaming output. All images are converted to JPEG.\n *\n * Features:\n * - Compress/resize images to fit under 1.5MB target\n * - Convert AVIF/WebP/PNG/JPEG to JPEG\n * - Memory-efficient scanline processing\n * - Smart quality optimization: quality first, dimensions last\n */\n\nimport { sip } from \"@standardagents/sip\";\n\n// Target 1.5MB binary → ~2MB base64 for RPC transfer\n// SQLite limit is 2MB per row, but base64 overhead means we need headroom\nconst MAX_SIZE = 1.5 * 1024 * 1024;\n\n// Memory safety limit\nconst MAX_DIMENSION = 4096;\n\n// Reject images that would consume too much memory before decoding\nconst MAX_INPUT_SIZE = 20 * 1024 * 1024; // 20MB\n\nexport interface ProcessedImage {\n data: ArrayBuffer;\n mimeType: \"image/jpeg\";\n width: number;\n height: number;\n}\n\n/**\n * Process an image to ensure it's under 1.5MB and in JPEG format.\n *\n * All images (including transparent PNGs) are converted to JPEG.\n * This trades transparency for reliability - SIP's WASM works correctly\n * in Cloudflare Workers while @jsquash's WASM loading fails.\n *\n * @param input - Raw image data as ArrayBuffer\n * @param inputMimeType - MIME type hint (unused, kept for API compatibility)\n * @returns Processed image data as JPEG with dimensions\n */\nexport async function processImage(\n input: ArrayBuffer,\n inputMimeType: string\n): Promise<ProcessedImage> {\n // Memory safety: reject very large images\n if (input.byteLength > MAX_INPUT_SIZE) {\n throw new Error(`Image too large: ${input.byteLength} bytes exceeds ${MAX_INPUT_SIZE} byte limit`);\n }\n\n // Use sip for ALL images - outputs JPEG\n const result = await sip.process(input, {\n maxWidth: MAX_DIMENSION,\n maxHeight: MAX_DIMENSION,\n maxBytes: MAX_SIZE,\n quality: 85,\n });\n\n return {\n data: result.data,\n mimeType: \"image/jpeg\",\n width: result.width,\n height: result.height,\n };\n}\n\n/**\n * Detect image format from magic bytes (fallback)\n */\nfunction detectFormat(data: ArrayBuffer, mimeType: string): string {\n const bytes = new Uint8Array(data.slice(0, 12));\n\n if (bytes[4] === 0x66 && bytes[5] === 0x74 && bytes[6] === 0x79 && bytes[7] === 0x70) {\n const brand = String.fromCharCode(...bytes.slice(8, 12));\n if (brand === \"avif\" || brand === \"avis\") return \"avif\";\n }\n\n if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4E && bytes[3] === 0x47) {\n return \"png\";\n }\n\n if (bytes[0] === 0xFF && bytes[1] === 0xD8 && bytes[2] === 0xFF) {\n return \"jpeg\";\n }\n\n if (bytes[0] === 0x52 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x46 &&\n bytes[8] === 0x57 && bytes[9] === 0x45 && bytes[10] === 0x42 && bytes[11] === 0x50) {\n return \"webp\";\n }\n\n if (mimeType.includes(\"png\")) return \"png\";\n if (mimeType.includes(\"webp\")) return \"webp\";\n if (mimeType.includes(\"avif\")) return \"avif\";\n return \"jpeg\";\n}\n\n/**\n * Check if an image needs processing based on size and format.\n *\n * @param data - Base64-encoded image data\n * @param mimeType - MIME type of the image\n * @returns true if processing is needed\n */\nexport function needsProcessing(data: string, mimeType: string): boolean {\n // Decode base64 to get actual size\n const binaryLength = Math.ceil(data.length * 3 / 4);\n\n // Process if >2MB or unsupported format\n return (\n binaryLength > MAX_SIZE ||\n mimeType.includes(\"avif\") ||\n mimeType.includes(\"webp\")\n );\n}\n\n/**\n * Convert base64 string to ArrayBuffer\n */\nexport function base64ToArrayBuffer(base64: string): ArrayBuffer {\n const binaryString = atob(base64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n return bytes.buffer;\n}\n\n/**\n * Convert ArrayBuffer to base64 string\n */\nexport function arrayBufferToBase64(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary);\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -739,6 +739,8 @@ interface FileRecord {
739
739
  metadata?: Record<string, unknown> | null;
740
740
  isDirectory: boolean;
741
741
  createdAt: number;
742
+ width?: number | null;
743
+ height?: number | null;
742
744
  }
743
745
  /**
744
746
  * Image-specific metadata stored in FileRecord.metadata
package/dist/index.js CHANGED
@@ -1,6 +1,4 @@
1
- import { probe, sip } from '@standardagents/sip';
2
- import { decode as decode$1, encode } from '@jsquash/png';
3
- import { decode } from '@jsquash/avif';
1
+ import { sip } from '@standardagents/sip';
4
2
  import fs2 from 'fs';
5
3
  import path3 from 'path';
6
4
  import { fileURLToPath } from 'url';
@@ -4603,7 +4601,9 @@ function rowToFileRecord(row) {
4603
4601
  size: row.size,
4604
4602
  metadata: row.metadata ? JSON.parse(row.metadata) : null,
4605
4603
  isDirectory: row.is_directory === 1,
4606
- createdAt: row.created_at
4604
+ createdAt: row.created_at,
4605
+ width: row.width ?? null,
4606
+ height: row.height ?? null
4607
4607
  };
4608
4608
  }
4609
4609
  function inferMimeType(filename) {
@@ -4674,8 +4674,8 @@ var init_files = __esm({
4674
4674
  }
4675
4675
  const metadataJson = options?.metadata ? JSON.stringify(options.metadata) : null;
4676
4676
  await this.sql.exec(
4677
- `INSERT OR REPLACE INTO files (path, name, mime_type, storage, location, data, content, size, metadata, thumbnail, is_directory, created_at)
4678
- VALUES (?, ?, ?, 'local', NULL, ?, ?, ?, ?, ?, 0, ?)`,
4677
+ `INSERT OR REPLACE INTO files (path, name, mime_type, storage, location, data, content, size, metadata, thumbnail, is_directory, created_at, width, height)
4678
+ VALUES (?, ?, ?, 'local', NULL, ?, ?, ?, ?, ?, 0, ?, ?, ?)`,
4679
4679
  normalizedPath,
4680
4680
  name,
4681
4681
  mimeType,
@@ -4684,7 +4684,9 @@ var init_files = __esm({
4684
4684
  size,
4685
4685
  metadataJson,
4686
4686
  options?.thumbnail || null,
4687
- now
4687
+ now,
4688
+ options?.width ?? null,
4689
+ options?.height ?? null
4688
4690
  );
4689
4691
  await this.updateStats();
4690
4692
  return {
@@ -4696,7 +4698,9 @@ var init_files = __esm({
4696
4698
  size,
4697
4699
  metadata: options?.metadata || null,
4698
4700
  isDirectory: false,
4699
- createdAt: now
4701
+ createdAt: now,
4702
+ width: options?.width ?? null,
4703
+ height: options?.height ?? null
4700
4704
  };
4701
4705
  }
4702
4706
  /**
@@ -4970,144 +4974,19 @@ async function processImage(input, inputMimeType) {
4970
4974
  if (input.byteLength > MAX_INPUT_SIZE) {
4971
4975
  throw new Error(`Image too large: ${input.byteLength} bytes exceeds ${MAX_INPUT_SIZE} byte limit`);
4972
4976
  }
4973
- const probeResult = probe(input);
4974
- const format = probeResult.format !== "unknown" ? probeResult.format : detectFormat(input, inputMimeType);
4975
- const hasAlpha = probeResult.hasAlpha;
4976
- if (hasAlpha && (format === "png" || format === "avif" || format === "webp")) {
4977
- return await processPngWithAlpha(input, format);
4978
- }
4979
- try {
4980
- const result = await sip.process(input, {
4981
- maxWidth: MAX_DIMENSION,
4982
- maxHeight: MAX_DIMENSION,
4983
- maxBytes: MAX_SIZE,
4984
- quality: 85
4985
- });
4986
- return {
4987
- data: result.data,
4988
- mimeType: "image/jpeg",
4989
- width: result.width,
4990
- height: result.height
4991
- };
4992
- } catch (err) {
4993
- console.error("[sip] Processing failed, falling back:", err);
4994
- throw err;
4995
- }
4996
- }
4997
- async function processPngWithAlpha(input, format) {
4998
- let imageData;
4999
- if (format === "avif") {
5000
- imageData = await decode(input);
5001
- } else {
5002
- imageData = await decode$1(input);
5003
- }
5004
- const originalWidth = imageData.width;
5005
- const originalHeight = imageData.height;
5006
- let encoded = await encode(imageData);
5007
- if (encoded.byteLength <= MAX_SIZE) {
5008
- return {
5009
- data: encoded,
5010
- mimeType: "image/png",
5011
- width: originalWidth,
5012
- height: originalHeight
5013
- };
5014
- }
5015
- let scale = 0.9;
5016
- while (encoded.byteLength > MAX_SIZE && scale > 0.15) {
5017
- const newWidth = Math.floor(originalWidth * scale);
5018
- const newHeight = Math.floor(originalHeight * scale);
5019
- if (newWidth < 100 || newHeight < 100) {
5020
- scale *= 0.9;
5021
- continue;
5022
- }
5023
- const resized = resizeRgba(
5024
- new Uint8ClampedArray(imageData.data),
5025
- originalWidth,
5026
- originalHeight,
5027
- newWidth,
5028
- newHeight
5029
- );
5030
- const resizedImageData = { data: resized, width: newWidth, height: newHeight };
5031
- encoded = await encode(resizedImageData);
5032
- if (encoded.byteLength <= MAX_SIZE) {
5033
- return {
5034
- data: encoded,
5035
- mimeType: "image/png",
5036
- width: newWidth,
5037
- height: newHeight
5038
- };
5039
- }
5040
- scale *= 0.9;
5041
- }
5042
- const finalWidth = Math.floor(originalWidth * 0.15);
5043
- const finalHeight = Math.floor(originalHeight * 0.15);
5044
- const finalResized = resizeRgba(
5045
- new Uint8ClampedArray(imageData.data),
5046
- originalWidth,
5047
- originalHeight,
5048
- finalWidth,
5049
- finalHeight
5050
- );
5051
- encoded = await encode({ data: finalResized, width: finalWidth, height: finalHeight });
4977
+ const result = await sip.process(input, {
4978
+ maxWidth: MAX_DIMENSION,
4979
+ maxHeight: MAX_DIMENSION,
4980
+ maxBytes: MAX_SIZE,
4981
+ quality: 85
4982
+ });
5052
4983
  return {
5053
- data: encoded,
5054
- mimeType: "image/png",
5055
- width: finalWidth,
5056
- height: finalHeight
4984
+ data: result.data,
4985
+ mimeType: "image/jpeg",
4986
+ width: result.width,
4987
+ height: result.height
5057
4988
  };
5058
4989
  }
5059
- function resizeRgba(src, srcWidth, srcHeight, dstWidth, dstHeight) {
5060
- const dst = new Uint8ClampedArray(dstWidth * dstHeight * 4);
5061
- const xScale = srcWidth / dstWidth;
5062
- const yScale = srcHeight / dstHeight;
5063
- for (let dstY = 0; dstY < dstHeight; dstY++) {
5064
- for (let dstX = 0; dstX < dstWidth; dstX++) {
5065
- const srcXFloat = dstX * xScale;
5066
- const srcYFloat = dstY * yScale;
5067
- const srcX0 = Math.floor(srcXFloat);
5068
- const srcY0 = Math.floor(srcYFloat);
5069
- const srcX1 = Math.min(srcX0 + 1, srcWidth - 1);
5070
- const srcY1 = Math.min(srcY0 + 1, srcHeight - 1);
5071
- const tx = srcXFloat - srcX0;
5072
- const ty = srcYFloat - srcY0;
5073
- const idx00 = (srcY0 * srcWidth + srcX0) * 4;
5074
- const idx10 = (srcY0 * srcWidth + srcX1) * 4;
5075
- const idx01 = (srcY1 * srcWidth + srcX0) * 4;
5076
- const idx11 = (srcY1 * srcWidth + srcX1) * 4;
5077
- const dstIdx = (dstY * dstWidth + dstX) * 4;
5078
- for (let c = 0; c < 4; c++) {
5079
- const v00 = src[idx00 + c];
5080
- const v10 = src[idx10 + c];
5081
- const v01 = src[idx01 + c];
5082
- const v11 = src[idx11 + c];
5083
- const top = v00 * (1 - tx) + v10 * tx;
5084
- const bottom = v01 * (1 - tx) + v11 * tx;
5085
- dst[dstIdx + c] = Math.round(top * (1 - ty) + bottom * ty);
5086
- }
5087
- }
5088
- }
5089
- return dst;
5090
- }
5091
- function detectFormat(data, mimeType) {
5092
- const bytes = new Uint8Array(data.slice(0, 12));
5093
- if (bytes[4] === 102 && bytes[5] === 116 && bytes[6] === 121 && bytes[7] === 112) {
5094
- const brand = String.fromCharCode(...bytes.slice(8, 12));
5095
- if (brand === "avif" || brand === "avis") return "avif";
5096
- }
5097
- if (bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71) {
5098
- return "png";
5099
- }
5100
- if (bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) {
5101
- return "jpeg";
5102
- }
5103
- if (bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80) {
5104
- return "webp";
5105
- }
5106
- if (mimeType.includes("png")) return "png";
5107
- if (mimeType.includes("webp")) return "webp";
5108
- if (mimeType.includes("avif")) return "avif";
5109
- return "jpeg";
5110
- }
5111
4990
  function needsProcessing(data, mimeType) {
5112
4991
  const binaryLength = Math.ceil(data.length * 3 / 4);
5113
4992
  return binaryLength > MAX_SIZE || mimeType.includes("avif") || mimeType.includes("webp");
@@ -7195,11 +7074,10 @@ function agentbuilder(options = {}) {
7195
7074
  // WASM image processing deps - must be excluded to avoid pre-bundle cache issues
7196
7075
  "@cf-wasm/photon",
7197
7076
  "@cf-wasm/photon/workerd",
7077
+ "@standardagents/sip",
7078
+ // sip's jsquash dependencies (WASM-based decoders)
7198
7079
  "@jsquash/avif",
7199
- "@jsquash/jpeg",
7200
- "@jsquash/png",
7201
- "@jsquash/webp",
7202
- "@standardagents/sip"
7080
+ "@jsquash/webp"
7203
7081
  ];
7204
7082
  const depsToInclude = [
7205
7083
  "zod",
@@ -7234,11 +7112,10 @@ function agentbuilder(options = {}) {
7234
7112
  // WASM image processing deps
7235
7113
  "@cf-wasm/photon",
7236
7114
  "@cf-wasm/photon/workerd",
7115
+ "@standardagents/sip",
7116
+ // sip's jsquash dependencies (WASM-based decoders)
7237
7117
  "@jsquash/avif",
7238
- "@jsquash/jpeg",
7239
- "@jsquash/png",
7240
- "@jsquash/webp",
7241
- "@standardagents/sip"
7118
+ "@jsquash/webp"
7242
7119
  ];
7243
7120
  const depsToInclude = [
7244
7121
  "zod",
@@ -9229,8 +9106,18 @@ var migration17 = {
9229
9106
  }
9230
9107
  };
9231
9108
 
9109
+ // src/durable-objects/migrations/018_add_image_dimensions.ts
9110
+ var migration18 = {
9111
+ version: 18,
9112
+ async up(sql) {
9113
+ await sql.exec(`ALTER TABLE files ADD COLUMN width INTEGER`);
9114
+ await sql.exec(`ALTER TABLE files ADD COLUMN height INTEGER`);
9115
+ await sql.exec(`UPDATE _metadata SET value = '18' WHERE key = 'schema_version'`);
9116
+ }
9117
+ };
9118
+
9232
9119
  // src/durable-objects/migrations/index.ts
9233
- var migrations = [migration, migration2, migration3, migration4, migration5, migration6, migration7, migration8, migration9, migration10, migration11, migration12, migration13, migration14, migration15, migration16, migration17];
9120
+ var migrations = [migration, migration2, migration3, migration4, migration5, migration6, migration7, migration8, migration9, migration10, migration11, migration12, migration13, migration14, migration15, migration16, migration17, migration18];
9234
9121
  var LATEST_SCHEMA_VERSION = migrations.length;
9235
9122
 
9236
9123
  // src/durable-objects/DurableThread.ts
@@ -9631,9 +9518,9 @@ var DurableThread = class extends DurableObject {
9631
9518
  * Each migration is run in order, starting from the current version + 1.
9632
9519
  */
9633
9520
  async runMigrations(fromVersion) {
9634
- for (const migration19 of migrations) {
9635
- if (migration19.version > fromVersion) {
9636
- await migration19.up(this.ctx.storage.sql);
9521
+ for (const migration20 of migrations) {
9522
+ if (migration20.version > fromVersion) {
9523
+ await migration20.up(this.ctx.storage.sql);
9637
9524
  }
9638
9525
  }
9639
9526
  }
@@ -10720,7 +10607,7 @@ var DurableThread = class extends DurableObject {
10720
10607
  id: message.id,
10721
10608
  role: message.role,
10722
10609
  content: message.content,
10723
- attachments: message.attachments,
10610
+ attachments: message.attachments ? JSON.parse(message.attachments) : null,
10724
10611
  created_at: message.created_at
10725
10612
  });
10726
10613
  const nextSide = role === "assistant" ? "b" : "a";
@@ -11179,7 +11066,7 @@ var DurableThread = class extends DurableObject {
11179
11066
  };
11180
11067
 
11181
11068
  // src/durable-objects/agentbuilder-migrations/0001_initial.ts
11182
- var migration18 = {
11069
+ var migration19 = {
11183
11070
  version: 1,
11184
11071
  async up(sql) {
11185
11072
  sql.exec(`
@@ -11278,7 +11165,7 @@ var migration18 = {
11278
11165
  };
11279
11166
 
11280
11167
  // src/durable-objects/agentbuilder-migrations/index.ts
11281
- var migrations2 = [migration18];
11168
+ var migrations2 = [migration19];
11282
11169
  var LATEST_SCHEMA_VERSION2 = 1;
11283
11170
 
11284
11171
  // src/durable-objects/DurableAgentBuilder.ts
@@ -11368,9 +11255,9 @@ var DurableAgentBuilder = class extends DurableObject {
11368
11255
  }
11369
11256
  }
11370
11257
  async runMigrations(fromVersion) {
11371
- for (const migration19 of migrations2) {
11372
- if (migration19.version > fromVersion) {
11373
- await migration19.up(this.ctx.storage.sql);
11258
+ for (const migration20 of migrations2) {
11259
+ if (migration20.version > fromVersion) {
11260
+ await migration20.up(this.ctx.storage.sql);
11374
11261
  }
11375
11262
  }
11376
11263
  }