cdp-toolkit 1.9.1 → 1.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1119,6 +1119,8 @@ async function clearMocks(args = {}) {
1119
1119
  }
1120
1120
 
1121
1121
  // src/driver.ts
1122
+ import { mkdir as mkdir3, readFile as readFile3, unlink as unlink3, writeFile as writeFile3 } from "node:fs/promises";
1123
+ import { join as join3 } from "node:path";
1122
1124
  var UID_STAMP_ATTR = "data-cdp-uid";
1123
1125
  var LEGACY_NUMERIC_UID = /^[0-9]+$/;
1124
1126
  var REQUIRED_CAPABILITIES = {
@@ -1148,6 +1150,41 @@ function interpolatePoints(from, to, steps) {
1148
1150
  out.push({ x: to.x, y: to.y });
1149
1151
  return out;
1150
1152
  }
1153
+ function isTiledCapture(delivery) {
1154
+ return delivery.tiled === true;
1155
+ }
1156
+ function viewportRecordFile(scheme, targetId) {
1157
+ return join3(leaseDir(), `viewport-${scheme}-${targetId.replace(/[^A-Za-z0-9._-]/g, "_")}.json`);
1158
+ }
1159
+ async function readViewportRecord(scheme, targetId) {
1160
+ try {
1161
+ const raw = JSON.parse(await readFile3(viewportRecordFile(scheme, targetId), "utf8"));
1162
+ if (typeof raw?.width !== "number" || typeof raw?.height !== "number")
1163
+ return;
1164
+ return {
1165
+ width: raw.width,
1166
+ height: raw.height,
1167
+ ...typeof raw.deviceScaleFactor === "number" ? { deviceScaleFactor: raw.deviceScaleFactor } : {},
1168
+ ...typeof raw.mobile === "boolean" ? { mobile: raw.mobile } : {}
1169
+ };
1170
+ } catch {
1171
+ return;
1172
+ }
1173
+ }
1174
+ async function writeViewportRecord(scheme, targetId, rec) {
1175
+ try {
1176
+ await mkdir3(leaseDir(), { recursive: true });
1177
+ await writeFile3(viewportRecordFile(scheme, targetId), JSON.stringify({ scheme, targetId, ...rec }), "utf8");
1178
+ } catch {}
1179
+ }
1180
+ function renderRestoreFailureNote(size, why) {
1181
+ return `— AND the ${size.width}x${size.height} render-size override could NOT be restored (${why}), so this page is STILL ` + `emulated at ${size.width}x${size.height}: put it back with emulate{width,height} or emulate{clearOverrides:true}.`;
1182
+ }
1183
+ async function clearViewportRecord(scheme, targetId) {
1184
+ try {
1185
+ await unlink3(viewportRecordFile(scheme, targetId));
1186
+ } catch {}
1187
+ }
1151
1188
 
1152
1189
  // src/cdp/driver.ts
1153
1190
  function driverError(code, message, data) {
@@ -1379,14 +1416,73 @@ async function backendNodeIdOf(conn, loc) {
1379
1416
  }
1380
1417
  return searchLocate(conn, "xpath" in loc ? loc.xpath : loc.text);
1381
1418
  }
1382
- async function fullPageClip(conn) {
1383
- const m = await conn.send("Page.getLayoutMetrics");
1419
+ async function layoutMetrics(conn) {
1420
+ return conn.send("Page.getLayoutMetrics");
1421
+ }
1422
+ function contentClip(m) {
1384
1423
  const size = m.cssContentSize ?? m.contentSize;
1385
1424
  if (!size)
1386
1425
  throw driverError("page-error", "Page.getLayoutMetrics returned no content size");
1387
1426
  return { x: 0, y: 0, width: Math.ceil(size.width), height: Math.ceil(size.height) };
1388
1427
  }
1389
- async function elementClip(conn, loc) {
1428
+ function devicePixelsPerCssPx(m) {
1429
+ const css = m.cssVisualViewport?.clientWidth ?? m.cssLayoutViewport?.clientWidth;
1430
+ const dev = m.visualViewport?.clientWidth ?? m.layoutViewport?.clientWidth;
1431
+ if (!css || !dev)
1432
+ return 1;
1433
+ const ratio = dev / css;
1434
+ return Number.isFinite(ratio) && ratio > 0 ? ratio : 1;
1435
+ }
1436
+ function scrollOffsetCssPx(m, devicePx) {
1437
+ const css = m.cssVisualViewport ?? m.cssLayoutViewport;
1438
+ if (css)
1439
+ return { x: css.pageX ?? 0, y: css.pageY ?? 0 };
1440
+ const legacy = m.layoutViewport;
1441
+ if (legacy)
1442
+ return { x: (legacy.pageX ?? 0) / devicePx, y: (legacy.pageY ?? 0) / devicePx };
1443
+ return { x: 0, y: 0 };
1444
+ }
1445
+ var MAX_ENCODED_SIDE_PX = 16384;
1446
+ function assertClipEncodable(clip, scale, devicePx, renderSize) {
1447
+ const outWidth = Math.ceil(clip.width * scale * devicePx);
1448
+ const outHeight = Math.ceil(clip.height * scale * devicePx);
1449
+ if (outWidth <= MAX_ENCODED_SIDE_PX && outHeight <= MAX_ENCODED_SIDE_PX)
1450
+ return;
1451
+ const at = renderSize ? ` at render size ${renderSize.width}x${renderSize.height}` : "";
1452
+ const projection = `(clip ${clip.width}x${clip.height} css px${at} x scale ${scale} x device pixel ratio ${devicePx}), ` + `past Chrome's ${MAX_ENCODED_SIDE_PX} px per-side limit; `;
1453
+ if (scale !== 1) {
1454
+ const fits = Math.floor(MAX_ENCODED_SIDE_PX / (Math.max(clip.width, clip.height) * devicePx) * 100) / 100;
1455
+ throw driverError("page-error", `screenshot scale ${scale} would encode ${outWidth}x${outHeight} px ${projection}` + (fits >= 0.01 ? `the largest scale that fits this clip is ${fits}.` : `this clip is already past the limit at scale 1 — capture a smaller region.`));
1456
+ }
1457
+ const maxCssSide = Math.floor(MAX_ENCODED_SIDE_PX / devicePx);
1458
+ throw driverError("page-error", `this capture would encode ${outWidth}x${outHeight} px ${projection}` + `at device pixel ratio ${devicePx} the clip may be at most ${maxCssSide} css px per side` + (renderSize ? ` — a narrow renderWidth reflows the document TALLER, so widen it, shorten the page, or drop fullPage.` : ` — capture a smaller region or drop fullPage.`));
1459
+ }
1460
+ function projectedSide(cssLength, scale, devicePx) {
1461
+ return Math.ceil(cssLength * scale * devicePx);
1462
+ }
1463
+ function planTiledCapture(clip, scale, devicePx, tile) {
1464
+ const untiled = { tiled: false, bandCount: 1, bandHeightCss: clip.height };
1465
+ if (tile === false)
1466
+ return untiled;
1467
+ const fits = projectedSide(clip.width, scale, devicePx) <= MAX_ENCODED_SIDE_PX && projectedSide(clip.height, scale, devicePx) <= MAX_ENCODED_SIDE_PX;
1468
+ if (fits && tile !== true)
1469
+ return untiled;
1470
+ const outWidth = projectedSide(clip.width, scale, devicePx);
1471
+ if (outWidth > MAX_ENCODED_SIDE_PX) {
1472
+ const maxCssWidth = Math.floor(MAX_ENCODED_SIDE_PX / (scale * devicePx));
1473
+ throw driverError("page-error", `this capture is ${outWidth} px WIDE (clip ${clip.width} css px x scale ${scale} x device pixel ratio ${devicePx}), ` + `past Chrome's ${MAX_ENCODED_SIDE_PX} px per-side limit, and width cannot be tiled: bands are stitched ` + `vertically only. Lower scale (at most ${Math.floor(MAX_ENCODED_SIDE_PX / (clip.width * devicePx) * 100) / 100}), ` + `or set renderWidth at most ${maxCssWidth} css px, or clip to a narrower element.`);
1474
+ }
1475
+ let bandHeightCss = Math.floor(MAX_ENCODED_SIDE_PX / (scale * devicePx));
1476
+ while (bandHeightCss > 1 && projectedSide(bandHeightCss, scale, devicePx) > MAX_ENCODED_SIDE_PX)
1477
+ bandHeightCss -= 1;
1478
+ if (bandHeightCss < 1) {
1479
+ throw driverError("page-error", `scale ${scale} at device pixel ratio ${devicePx} projects a single css pixel to ${scale * devicePx} device px, ` + `past the ${MAX_ENCODED_SIDE_PX} px limit — no band height can fit. Lower scale.`);
1480
+ }
1481
+ if (tile === true && clip.height <= bandHeightCss)
1482
+ bandHeightCss = Math.max(1, Math.ceil(clip.height / 2));
1483
+ return { tiled: true, bandCount: Math.max(1, Math.ceil(clip.height / bandHeightCss)), bandHeightCss };
1484
+ }
1485
+ async function elementBox(conn, loc) {
1390
1486
  const backendNodeId = await backendNodeIdOf(conn, loc);
1391
1487
  await conn.send("DOM.scrollIntoViewIfNeeded", { backendNodeId }).catch(() => {
1392
1488
  return;
@@ -1403,6 +1499,10 @@ async function elementClip(conn, loc) {
1403
1499
  throw driverError("page-error", "resolved element has zero area");
1404
1500
  return { x: minX, y: minY, width, height };
1405
1501
  }
1502
+ function documentClip(box, m, devicePx) {
1503
+ const scroll = scrollOffsetCssPx(m, devicePx);
1504
+ return { x: box.x + scroll.x, y: box.y + scroll.y, width: box.width, height: box.height };
1505
+ }
1406
1506
  var CDP_CAPABILITIES = new Set([
1407
1507
  "trace.performance",
1408
1508
  "heap.snapshot",
@@ -1413,6 +1513,9 @@ var CDP_CAPABILITIES = new Set([
1413
1513
  "emulate.networkConditions",
1414
1514
  "screenshot.fullPage",
1415
1515
  "screenshot.element",
1516
+ "screenshot.scale",
1517
+ "screenshot.renderSize",
1518
+ "screenshot.tile",
1416
1519
  "network.intercept",
1417
1520
  "snapshot.accessibilityTree",
1418
1521
  "input.insertTextAtomic",
@@ -1703,31 +1806,128 @@ class CdpPageDriver {
1703
1806
  const { objectId } = await resolveElementLocator(this.conn, loc);
1704
1807
  await this.conn.send("DOM.setFileInputFiles", { files: [...files], objectId });
1705
1808
  }
1706
- async screenshot(opts) {
1809
+ async screenshot(opts, consumeBands) {
1810
+ const renderSize = opts?.renderWidth !== undefined && opts?.renderHeight !== undefined ? { width: opts.renderWidth, height: opts.renderHeight } : undefined;
1811
+ if (!renderSize)
1812
+ return this.capture(opts, consumeBands);
1813
+ const previous = await readViewportRecord(this.browser.scheme, this.info.id);
1814
+ await this.conn.send("Emulation.setDeviceMetricsOverride", {
1815
+ width: renderSize.width,
1816
+ height: renderSize.height,
1817
+ deviceScaleFactor: previous?.deviceScaleFactor ?? 0,
1818
+ mobile: previous?.mobile ?? false
1819
+ });
1820
+ let shot;
1821
+ let captureError;
1822
+ try {
1823
+ shot = await this.capture(opts, consumeBands);
1824
+ } catch (e) {
1825
+ captureError = e;
1826
+ }
1827
+ let restoreError;
1828
+ try {
1829
+ if (previous) {
1830
+ await this.conn.send("Emulation.setDeviceMetricsOverride", {
1831
+ width: previous.width,
1832
+ height: previous.height,
1833
+ deviceScaleFactor: previous.deviceScaleFactor ?? 0,
1834
+ mobile: previous.mobile ?? false
1835
+ });
1836
+ } else {
1837
+ await this.conn.send("Emulation.clearDeviceMetricsOverride");
1838
+ }
1839
+ } catch (e) {
1840
+ restoreError = e instanceof Error ? e.message : String(e);
1841
+ }
1842
+ if (!shot) {
1843
+ if (restoreError !== undefined && captureError instanceof Error) {
1844
+ captureError.message = `${captureError.message} ${renderRestoreFailureNote(renderSize, restoreError)}`;
1845
+ }
1846
+ throw captureError;
1847
+ }
1848
+ const result = Object.assign(shot, { renderSize, renderRestored: restoreError === undefined });
1849
+ if (restoreError !== undefined)
1850
+ result.renderRestoreError = restoreError;
1851
+ return result;
1852
+ }
1853
+ async capture(opts, consumeBands) {
1707
1854
  await this.ensureDomain("Page");
1708
1855
  const format = opts?.format ?? "png";
1709
1856
  const quality = format === "jpeg" ? opts?.quality ?? 80 : undefined;
1857
+ const scale = opts?.scale ?? 1;
1858
+ const renderSize = opts?.renderWidth !== undefined && opts?.renderHeight !== undefined ? { width: opts.renderWidth, height: opts.renderHeight } : undefined;
1859
+ const box = opts?.clip ? await elementBox(this.conn, opts.clip) : undefined;
1860
+ const metrics = await layoutMetrics(this.conn);
1861
+ const devicePx = devicePixelsPerCssPx(metrics);
1862
+ const clip = box ? documentClip(box, metrics, devicePx) : contentClip(metrics);
1863
+ const sendsClip = opts?.clip !== undefined || opts?.fullPage === true || scale !== 1 || renderSize !== undefined;
1864
+ if (opts?.tile === true && !consumeBands) {
1865
+ throw driverError("unsupported", "screenshot tile:true needs a caller that can consume bands; this one passed no band consumer");
1866
+ }
1867
+ const plan = planTiledCapture(clip, scale, devicePx, consumeBands ? opts?.tile : false);
1868
+ if (plan.tiled)
1869
+ return this.captureTiled(clip, plan, scale, format, consumeBands);
1870
+ assertClipEncodable(clip, scale, devicePx, renderSize);
1710
1871
  const params = { format, captureBeyondViewport: true };
1711
1872
  if (quality != null)
1712
1873
  params.quality = quality;
1713
- if (opts?.clip)
1714
- params.clip = { ...await elementClip(this.conn, opts.clip), scale: 1 };
1715
- else if (opts?.fullPage)
1716
- params.clip = { ...await fullPageClip(this.conn), scale: 1 };
1874
+ if (sendsClip)
1875
+ params.clip = { ...clip, scale };
1717
1876
  const { data } = await this.conn.send("Page.captureScreenshot", params);
1718
1877
  return { data: new Uint8Array(Buffer.from(data, "base64")), format };
1719
1878
  }
1879
+ async captureTiled(clip, plan, scale, format, consumeBands) {
1880
+ const capture = {
1881
+ bands: this.bands(clip, plan, scale, format),
1882
+ bandCount: plan.bandCount,
1883
+ bandHeightCss: plan.bandHeightCss,
1884
+ clip,
1885
+ format: "png"
1886
+ };
1887
+ const consumed = await consumeBands(capture);
1888
+ return { tiled: true, format: "png", bandCount: plan.bandCount, bandHeightCss: plan.bandHeightCss, consumed };
1889
+ }
1890
+ async* bands(clip, plan, scale, format) {
1891
+ if (format !== "png") {
1892
+ throw driverError("unsupported", `a tiled capture is PNG-only (bands are stitched as PNG scanlines); got format "${format}"`);
1893
+ }
1894
+ const bottom = clip.y + clip.height;
1895
+ for (let i = 0;i < plan.bandCount; i++) {
1896
+ const y = clip.y + i * plan.bandHeightCss;
1897
+ const height = Math.min(plan.bandHeightCss, bottom - y);
1898
+ const { data } = await this.conn.send("Page.captureScreenshot", {
1899
+ format: "png",
1900
+ captureBeyondViewport: true,
1901
+ clip: { x: clip.x, y, width: clip.width, height, scale }
1902
+ });
1903
+ yield new Uint8Array(Buffer.from(data, "base64"));
1904
+ }
1905
+ }
1720
1906
  async emulate(opts) {
1721
1907
  if (opts.clearOverrides) {
1722
1908
  const c = () => {
1723
1909
  return;
1724
1910
  };
1725
- await this.conn.send("Emulation.clearDeviceMetricsOverride").catch(c);
1911
+ let metricsClearError;
1912
+ try {
1913
+ await this.ensureDomain("Page");
1914
+ const metrics = await layoutMetrics(this.conn);
1915
+ const width = metrics.cssVisualViewport?.clientWidth ?? metrics.cssLayoutViewport?.clientWidth ?? metrics.visualViewport?.clientWidth ?? metrics.layoutViewport?.clientWidth ?? 1;
1916
+ const height = metrics.cssVisualViewport?.clientHeight ?? metrics.cssLayoutViewport?.clientHeight ?? metrics.visualViewport?.clientHeight ?? metrics.layoutViewport?.clientHeight ?? 1;
1917
+ await this.conn.send("Emulation.setDeviceMetricsOverride", { width, height, deviceScaleFactor: 0, mobile: false });
1918
+ await this.conn.send("Emulation.clearDeviceMetricsOverride");
1919
+ } catch (e) {
1920
+ metricsClearError = e instanceof Error ? e.message : String(e);
1921
+ }
1726
1922
  await this.conn.send("Emulation.setUserAgentOverride", { userAgent: "" }).catch(c);
1727
1923
  await this.conn.send("Emulation.setCPUThrottlingRate", { rate: 1 }).catch(c);
1728
1924
  await this.conn.send("Emulation.setEmulatedMedia", { media: "", features: [] }).catch(c);
1729
1925
  await this.ensureDomainSoft("Network");
1730
1926
  await this.conn.send("Network.emulateNetworkConditions", { offline: false, latency: 0, downloadThroughput: -1, uploadThroughput: -1 }).catch(c);
1927
+ if (metricsClearError !== undefined) {
1928
+ throw driverError("page-error", `emulate: could not verify the device-metrics override was cleared (${metricsClearError})`);
1929
+ }
1930
+ await clearViewportRecord(this.browser.scheme, this.info.id);
1731
1931
  return { applied: [] };
1732
1932
  }
1733
1933
  const applied = [];
@@ -1741,6 +1941,12 @@ class CdpPageDriver {
1741
1941
  deviceScaleFactor: opts.deviceScaleFactor ?? 0,
1742
1942
  mobile: opts.mobile ?? false
1743
1943
  });
1944
+ await writeViewportRecord(this.browser.scheme, this.info.id, {
1945
+ width: opts.width,
1946
+ height: opts.height,
1947
+ deviceScaleFactor: opts.deviceScaleFactor ?? 0,
1948
+ mobile: opts.mobile ?? false
1949
+ });
1744
1950
  applied.push("deviceMetrics");
1745
1951
  }
1746
1952
  if (opts.userAgent != null) {
@@ -2128,8 +2334,522 @@ async function reapStaleAgentTabs(driver, backend) {
2128
2334
  }
2129
2335
 
2130
2336
  // src/shared-tools.ts
2131
- import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
2132
- import { dirname, join as join3 } from "node:path";
2337
+ import { mkdir as mkdir5, writeFile as writeFile4 } from "node:fs/promises";
2338
+ import { dirname as dirname2, join as join4 } from "node:path";
2339
+
2340
+ // src/png.ts
2341
+ import { once } from "node:events";
2342
+ import { mkdir as mkdir4, open, rename, rm } from "node:fs/promises";
2343
+ import { dirname } from "node:path";
2344
+ import { createDeflate, createInflate, deflateSync } from "node:zlib";
2345
+ var PNG_SIGNATURE = Uint8Array.of(137, 80, 78, 71, 13, 10, 26, 10);
2346
+ var ZLIB_CHUNK = 1 << 18;
2347
+ var BLOCK_RAW_BYTES = 1 << 22;
2348
+ var MAX_BLOCK_ROWS = 4096;
2349
+ var DECIDE_LEVEL = 6;
2350
+ var CARRIED_CHUNKS = ["gAMA", "cHRM", "sRGB", "iCCP", "pHYs"];
2351
+ var CHANNELS = { 0: 1, 2: 3, 4: 2, 6: 4 };
2352
+ var LEGAL_DEPTHS = {
2353
+ 0: [1, 2, 4, 8, 16],
2354
+ 2: [8, 16],
2355
+ 4: [8, 16],
2356
+ 6: [8, 16]
2357
+ };
2358
+ var COLOUR_TYPE_NAMES = {
2359
+ 0: "greyscale",
2360
+ 2: "truecolour RGB",
2361
+ 3: "palette",
2362
+ 4: "greyscale+alpha",
2363
+ 6: "RGBA"
2364
+ };
2365
+
2366
+ class PngStitchError extends Error {
2367
+ constructor(message) {
2368
+ super(message);
2369
+ this.name = "PngStitchError";
2370
+ }
2371
+ }
2372
+ var CRC_TABLE = (() => {
2373
+ const table = new Uint32Array(256);
2374
+ for (let n = 0;n < 256; n++) {
2375
+ let c = n;
2376
+ for (let k = 0;k < 8; k++)
2377
+ c = (c & 1) !== 0 ? 3988292384 ^ c >>> 1 : c >>> 1;
2378
+ table[n] = c >>> 0;
2379
+ }
2380
+ return table;
2381
+ })();
2382
+ function crcUpdate(crc, bytes) {
2383
+ let c = crc;
2384
+ for (let i = 0;i < bytes.length; i++)
2385
+ c = CRC_TABLE[(c ^ bytes[i]) & 255] ^ c >>> 8;
2386
+ return c >>> 0;
2387
+ }
2388
+ function crc32(bytes) {
2389
+ return (crcUpdate(4294967295, bytes) ^ 4294967295) >>> 0;
2390
+ }
2391
+ function readU32(b, off) {
2392
+ return (b[off] << 24 | b[off + 1] << 16 | b[off + 2] << 8 | b[off + 3]) >>> 0;
2393
+ }
2394
+ function chunkTypeAt(b, off) {
2395
+ return String.fromCharCode(b[off], b[off + 1], b[off + 2], b[off + 3]);
2396
+ }
2397
+ function sameBytes(a, b) {
2398
+ if (a.length !== b.length)
2399
+ return false;
2400
+ for (let i = 0;i < a.length; i++)
2401
+ if (a[i] !== b[i])
2402
+ return false;
2403
+ return true;
2404
+ }
2405
+ function paeth(a, b, c) {
2406
+ const p = a + b - c;
2407
+ const pa = p > a ? p - a : a - p;
2408
+ const pb = p > b ? p - b : b - p;
2409
+ const pc = p > c ? p - c : c - p;
2410
+ if (pa <= pb && pa <= pc)
2411
+ return a;
2412
+ return pb <= pc ? b : c;
2413
+ }
2414
+ function unfilterRow(type, row, prev, bpp, at) {
2415
+ const n = row.length;
2416
+ switch (type) {
2417
+ case 0:
2418
+ return;
2419
+ case 1:
2420
+ for (let i = bpp;i < n; i++)
2421
+ row[i] = row[i] + row[i - bpp] & 255;
2422
+ return;
2423
+ case 2:
2424
+ for (let i = 0;i < n; i++)
2425
+ row[i] = row[i] + prev[i] & 255;
2426
+ return;
2427
+ case 3:
2428
+ for (let i = 0;i < n; i++) {
2429
+ const a = i >= bpp ? row[i - bpp] : 0;
2430
+ row[i] = row[i] + (a + prev[i] >> 1) & 255;
2431
+ }
2432
+ return;
2433
+ case 4:
2434
+ for (let i = 0;i < n; i++) {
2435
+ const a = i >= bpp ? row[i - bpp] : 0;
2436
+ const c = i >= bpp ? prev[i - bpp] : 0;
2437
+ row[i] = row[i] + paeth(a, prev[i], c) & 255;
2438
+ }
2439
+ return;
2440
+ default:
2441
+ throw new PngStitchError(`${at}: unknown row filter type ${type} (PNG defines 0-4)`);
2442
+ }
2443
+ }
2444
+ function absSigned(v) {
2445
+ const u = v & 255;
2446
+ return u < 128 ? u : 256 - u;
2447
+ }
2448
+ function chooseFilter(row, prev, bpp) {
2449
+ const n = row.length;
2450
+ let s0 = 0, s1 = 0, s2 = 0, s3 = 0, s4 = 0;
2451
+ for (let i = 0;i < n; i++) {
2452
+ const x = row[i];
2453
+ const b = prev[i];
2454
+ const a = i >= bpp ? row[i - bpp] : 0;
2455
+ const c = i >= bpp ? prev[i - bpp] : 0;
2456
+ s0 += absSigned(x);
2457
+ s1 += absSigned(x - a);
2458
+ s2 += absSigned(x - b);
2459
+ s3 += absSigned(x - (a + b >> 1));
2460
+ s4 += absSigned(x - paeth(a, b, c));
2461
+ }
2462
+ let best = 0, bestSum = s0;
2463
+ if (s1 < bestSum) {
2464
+ best = 1;
2465
+ bestSum = s1;
2466
+ }
2467
+ if (s2 < bestSum) {
2468
+ best = 2;
2469
+ bestSum = s2;
2470
+ }
2471
+ if (s3 < bestSum) {
2472
+ best = 3;
2473
+ bestSum = s3;
2474
+ }
2475
+ if (s4 < bestSum) {
2476
+ best = 4;
2477
+ }
2478
+ return best;
2479
+ }
2480
+ function writeFilteredRow(type, row, prev, bpp, out, off) {
2481
+ const n = row.length;
2482
+ switch (type) {
2483
+ case 0:
2484
+ out.set(row, off);
2485
+ return;
2486
+ case 1:
2487
+ for (let i = 0;i < n; i++)
2488
+ out[off + i] = row[i] - (i >= bpp ? row[i - bpp] : 0) & 255;
2489
+ return;
2490
+ case 2:
2491
+ for (let i = 0;i < n; i++)
2492
+ out[off + i] = row[i] - prev[i] & 255;
2493
+ return;
2494
+ case 3:
2495
+ for (let i = 0;i < n; i++) {
2496
+ const a = i >= bpp ? row[i - bpp] : 0;
2497
+ out[off + i] = row[i] - (a + prev[i] >> 1) & 255;
2498
+ }
2499
+ return;
2500
+ default:
2501
+ for (let i = 0;i < n; i++) {
2502
+ const a = i >= bpp ? row[i - bpp] : 0;
2503
+ const c = i >= bpp ? prev[i - bpp] : 0;
2504
+ out[off + i] = row[i] - paeth(a, prev[i], c) & 255;
2505
+ }
2506
+ return;
2507
+ }
2508
+ }
2509
+ function filterUnitBytes(h) {
2510
+ return Math.max(1, Math.ceil(CHANNELS[h.colourType] * h.bitDepth / 8));
2511
+ }
2512
+ function bytesPerRow(h) {
2513
+ return Math.ceil(h.width * CHANNELS[h.colourType] * h.bitDepth / 8);
2514
+ }
2515
+ function readIhdr(band, dataStart, at) {
2516
+ const width = readU32(band, dataStart);
2517
+ const height = readU32(band, dataStart + 4);
2518
+ const bitDepth = band[dataStart + 8];
2519
+ const colourType = band[dataStart + 9];
2520
+ const compression = band[dataStart + 10];
2521
+ const filterMethod = band[dataStart + 11];
2522
+ const interlace = band[dataStart + 12];
2523
+ if (width < 1 || height < 1)
2524
+ throw new PngStitchError(`${at}: IHDR declares ${width}x${height}; both dimensions must be >= 1`);
2525
+ if (compression !== 0)
2526
+ throw new PngStitchError(`${at}: IHDR compression method ${compression}; PNG defines only 0 (deflate)`);
2527
+ if (filterMethod !== 0)
2528
+ throw new PngStitchError(`${at}: IHDR filter method ${filterMethod}; PNG defines only 0`);
2529
+ if (interlace === 1) {
2530
+ throw new PngStitchError(`${at}: interlaced (Adam7) PNG is not supported. Its seven reduced passes are not scanlines of the final image, ` + `so bands of them cannot be concatenated; re-capture without interlacing (Chrome's Page.captureScreenshot always does).`);
2531
+ }
2532
+ if (interlace !== 0)
2533
+ throw new PngStitchError(`${at}: IHDR interlace method ${interlace}; PNG defines only 0 (none) and 1 (Adam7)`);
2534
+ if (colourType === 3) {
2535
+ throw new PngStitchError(`${at}: colour type 3 (palette) is not supported. Concatenating palettized bands requires every band's PLTE/tRNS to ` + `be identical or a full palette remap; Chrome emits colour type 2, so this path is refused rather than guessed.`);
2536
+ }
2537
+ if (CHANNELS[colourType] === undefined)
2538
+ throw new PngStitchError(`${at}: IHDR colour type ${colourType}; PNG defines 0, 2, 3, 4 and 6`);
2539
+ if (!LEGAL_DEPTHS[colourType].includes(bitDepth)) {
2540
+ throw new PngStitchError(`${at}: bit depth ${bitDepth} is not legal for colour type ${colourType} (${COLOUR_TYPE_NAMES[colourType]}); ` + `allowed: ${LEGAL_DEPTHS[colourType].join(", ")}`);
2541
+ }
2542
+ return { width, height, bitDepth, colourType };
2543
+ }
2544
+ function parseBand(band, index) {
2545
+ const at = `band ${index}`;
2546
+ if (band.length < 8)
2547
+ throw new PngStitchError(`${at}: ${band.length} bytes is too short to be a PNG`);
2548
+ for (let i = 0;i < 8; i++) {
2549
+ if (band[i] !== PNG_SIGNATURE[i])
2550
+ throw new PngStitchError(`${at}: not a PNG (bad 8-byte signature)`);
2551
+ }
2552
+ let header;
2553
+ const idat = [];
2554
+ const carried = [];
2555
+ let sawIend = false;
2556
+ let off = 8;
2557
+ while (off + 8 <= band.length) {
2558
+ const len = readU32(band, off);
2559
+ const type = chunkTypeAt(band, off + 4);
2560
+ const dataStart = off + 8;
2561
+ const dataEnd = dataStart + len;
2562
+ if (dataEnd + 4 > band.length) {
2563
+ throw new PngStitchError(`${at}: chunk "${type}" at byte ${off} claims ${len} payload bytes but only ${band.length - dataStart} remain (truncated PNG)`);
2564
+ }
2565
+ if (type === "IHDR") {
2566
+ if (off !== 8)
2567
+ throw new PngStitchError(`${at}: IHDR must be the first chunk, found it at byte ${off}`);
2568
+ if (len !== 13)
2569
+ throw new PngStitchError(`${at}: IHDR payload is ${len} bytes, must be 13`);
2570
+ const want = readU32(band, dataEnd);
2571
+ const got = crc32(band.subarray(off + 4, dataEnd));
2572
+ if (want !== got)
2573
+ throw new PngStitchError(`${at}: IHDR CRC mismatch (file says 0x${want.toString(16)}, computed 0x${got.toString(16)})`);
2574
+ header = readIhdr(band, dataStart, at);
2575
+ } else if (!header) {
2576
+ throw new PngStitchError(`${at}: first chunk is "${type}", expected IHDR`);
2577
+ } else if (type === "IDAT") {
2578
+ idat.push([dataStart, dataEnd]);
2579
+ } else if (type === "IEND") {
2580
+ sawIend = true;
2581
+ break;
2582
+ } else if (CARRIED_CHUNKS.includes(type)) {
2583
+ carried.push({ type, data: band.subarray(dataStart, dataEnd) });
2584
+ }
2585
+ off = dataEnd + 4;
2586
+ }
2587
+ if (!header)
2588
+ throw new PngStitchError(`${at}: no IHDR chunk`);
2589
+ if (idat.length === 0)
2590
+ throw new PngStitchError(`${at}: no IDAT chunk`);
2591
+ if (!sawIend)
2592
+ throw new PngStitchError(`${at}: no IEND chunk (truncated PNG)`);
2593
+ return { header, idat, carried };
2594
+ }
2595
+ function assertCompatible(first, next, firstCarried, nextCarried, index) {
2596
+ const at = `band ${index}`;
2597
+ if (next.width !== first.width)
2598
+ throw new PngStitchError(`${at}: width ${next.width} does not match band 1's width ${first.width}`);
2599
+ if (next.bitDepth !== first.bitDepth)
2600
+ throw new PngStitchError(`${at}: bit depth ${next.bitDepth} does not match band 1's bit depth ${first.bitDepth}`);
2601
+ if (next.colourType !== first.colourType) {
2602
+ throw new PngStitchError(`${at}: colour type ${next.colourType} (${COLOUR_TYPE_NAMES[next.colourType] ?? "?"}) does not match band 1's ` + `colour type ${first.colourType} (${COLOUR_TYPE_NAMES[first.colourType] ?? "?"})`);
2603
+ }
2604
+ for (const type of CARRIED_CHUNKS) {
2605
+ const a = firstCarried.find((c) => c.type === type);
2606
+ const b = nextCarried.find((c) => c.type === type);
2607
+ if (!a && !b)
2608
+ continue;
2609
+ if (!a || !b)
2610
+ throw new PngStitchError(`${at}: ${b ? "has" : "is missing"} a "${type}" chunk that band 1 ${b ? "does not have" : "has"}; the bands disagree about how their samples are interpreted`);
2611
+ if (!sameBytes(a.data, b.data))
2612
+ throw new PngStitchError(`${at}: its "${type}" chunk differs from band 1's; the bands disagree about how their samples are interpreted`);
2613
+ }
2614
+ }
2615
+
2616
+ class ChunkWriter {
2617
+ fh;
2618
+ pos = 0;
2619
+ constructor(fh) {
2620
+ this.fh = fh;
2621
+ }
2622
+ get bytes() {
2623
+ return this.pos;
2624
+ }
2625
+ async writeRaw(b) {
2626
+ await this.fh.write(b, 0, b.length, this.pos);
2627
+ this.pos += b.length;
2628
+ }
2629
+ async writeChunk(type, payload) {
2630
+ const head = Buffer.allocUnsafe(8);
2631
+ head.writeUInt32BE(payload.length, 0);
2632
+ head.write(type, 4, "latin1");
2633
+ const crc = (crcUpdate(crcUpdate(4294967295, head.subarray(4)), payload) ^ 4294967295) >>> 0;
2634
+ const tail = Buffer.allocUnsafe(4);
2635
+ tail.writeUInt32BE(crc, 0);
2636
+ await this.fh.writev([head, payload, tail], this.pos);
2637
+ this.pos += 8 + payload.length + 4;
2638
+ }
2639
+ async patchAt(position, b) {
2640
+ await this.fh.write(b, 0, b.length, position);
2641
+ }
2642
+ }
2643
+ function ihdrChunk(h, height) {
2644
+ const buf = Buffer.allocUnsafe(25);
2645
+ buf.writeUInt32BE(13, 0);
2646
+ buf.write("IHDR", 4, "latin1");
2647
+ buf.writeUInt32BE(h.width, 8);
2648
+ buf.writeUInt32BE(height, 12);
2649
+ buf[16] = h.bitDepth;
2650
+ buf[17] = h.colourType;
2651
+ buf[18] = 0;
2652
+ buf[19] = 0;
2653
+ buf[20] = 0;
2654
+ buf.writeUInt32BE(crc32(buf.subarray(4, 21)), 21);
2655
+ return buf;
2656
+ }
2657
+
2658
+ class RowEncoder {
2659
+ writer;
2660
+ bpr;
2661
+ bpp;
2662
+ deflate = createDeflate({ chunkSize: ZLIB_CHUNK });
2663
+ pump;
2664
+ rowsPerBlock;
2665
+ block;
2666
+ blockRows = 0;
2667
+ candNone;
2668
+ candHeuristic;
2669
+ prevRaw;
2670
+ constructor(writer, bpr, bpp) {
2671
+ this.writer = writer;
2672
+ this.bpr = bpr;
2673
+ this.bpp = bpp;
2674
+ this.rowsPerBlock = Math.min(MAX_BLOCK_ROWS, Math.max(1, Math.floor(BLOCK_RAW_BYTES / bpr)));
2675
+ this.block = Buffer.allocUnsafe(this.rowsPerBlock * bpr);
2676
+ this.candNone = Buffer.allocUnsafe(this.rowsPerBlock * (bpr + 1));
2677
+ this.candHeuristic = Buffer.allocUnsafe(this.rowsPerBlock * (bpr + 1));
2678
+ this.prevRaw = new Uint8Array(bpr);
2679
+ this.pump = (async () => {
2680
+ for await (const chunk of this.deflate)
2681
+ await this.writer.writeChunk("IDAT", chunk);
2682
+ })();
2683
+ this.pump.catch(() => {});
2684
+ }
2685
+ async addRow(raw) {
2686
+ this.block.set(raw, this.blockRows * this.bpr);
2687
+ this.blockRows += 1;
2688
+ if (this.blockRows === this.rowsPerBlock)
2689
+ await this.emitBlock();
2690
+ }
2691
+ async emitBlock() {
2692
+ const n = this.blockRows;
2693
+ if (n === 0)
2694
+ return;
2695
+ const { bpr, bpp } = this;
2696
+ const stride = bpr + 1;
2697
+ let prev = this.prevRaw;
2698
+ for (let r = 0;r < n; r++) {
2699
+ const row = this.block.subarray(r * bpr, (r + 1) * bpr);
2700
+ const at = r * stride;
2701
+ this.candNone[at] = 0;
2702
+ this.candNone.set(row, at + 1);
2703
+ const filter = chooseFilter(row, prev, bpp);
2704
+ this.candHeuristic[at] = filter;
2705
+ writeFilteredRow(filter, row, prev, bpp, this.candHeuristic, at + 1);
2706
+ prev = row;
2707
+ }
2708
+ const used = n * stride;
2709
+ const a = this.candNone.subarray(0, used);
2710
+ const b = this.candHeuristic.subarray(0, used);
2711
+ const heuristicWins = deflateSync(b, { level: DECIDE_LEVEL }).length < deflateSync(a, { level: DECIDE_LEVEL }).length;
2712
+ this.prevRaw.set(prev);
2713
+ this.blockRows = 0;
2714
+ const out = Buffer.from(heuristicWins ? b : a);
2715
+ if (!this.deflate.write(out))
2716
+ await once(this.deflate, "drain");
2717
+ }
2718
+ async finish() {
2719
+ await this.emitBlock();
2720
+ this.deflate.end();
2721
+ await this.pump;
2722
+ }
2723
+ async abort() {
2724
+ this.deflate.destroy();
2725
+ await this.pump.catch(() => {});
2726
+ }
2727
+ }
2728
+ async function* inflateBandIdat(band, slices, at) {
2729
+ const inf = createInflate({ chunkSize: ZLIB_CHUNK });
2730
+ const feed = (async () => {
2731
+ for (const [start, end] of slices) {
2732
+ const view = Buffer.from(band.buffer, band.byteOffset + start, end - start);
2733
+ if (!inf.write(view))
2734
+ await once(inf, "drain");
2735
+ }
2736
+ inf.end();
2737
+ })();
2738
+ feed.catch(() => {});
2739
+ try {
2740
+ for await (const chunk of inf)
2741
+ yield chunk;
2742
+ } catch (err) {
2743
+ throw new PngStitchError(`${at}: IDAT stream would not inflate (${err.message})`);
2744
+ } finally {
2745
+ inf.destroy();
2746
+ }
2747
+ }
2748
+ function isAsyncIterable(x) {
2749
+ return typeof x === "object" && x !== null && Symbol.asyncIterator in x;
2750
+ }
2751
+ async function* eachBand(bands) {
2752
+ if (isAsyncIterable(bands)) {
2753
+ for await (const b of bands)
2754
+ yield b;
2755
+ } else {
2756
+ for (const b of bands)
2757
+ yield b;
2758
+ }
2759
+ }
2760
+ async function stitchPngBandsToFile(bands, outPath) {
2761
+ await mkdir4(dirname(outPath), { recursive: true });
2762
+ const partPath = `${outPath}.stitch-partial`;
2763
+ const fh = await open(partPath, "w");
2764
+ let done = false;
2765
+ try {
2766
+ const result = await stitchInto(fh, bands);
2767
+ await fh.close();
2768
+ await rename(partPath, outPath);
2769
+ done = true;
2770
+ return result;
2771
+ } finally {
2772
+ if (!done) {
2773
+ await fh.close().catch(() => {});
2774
+ await rm(partPath, { force: true }).catch(() => {});
2775
+ }
2776
+ }
2777
+ }
2778
+ async function stitchInto(fh, bands) {
2779
+ const writer = new ChunkWriter(fh);
2780
+ let first;
2781
+ let firstCarried = [];
2782
+ let encoder;
2783
+ let prevRow = new Uint8Array(0);
2784
+ let rowBuf = new Uint8Array(0);
2785
+ let bpp = 0;
2786
+ let bpr = 0;
2787
+ let totalRows = 0;
2788
+ let index = 0;
2789
+ try {
2790
+ for await (const band of eachBand(bands)) {
2791
+ index += 1;
2792
+ const parsed = parseBand(band, index);
2793
+ const at = `band ${index}`;
2794
+ if (!first) {
2795
+ first = parsed.header;
2796
+ firstCarried = parsed.carried;
2797
+ bpp = filterUnitBytes(first);
2798
+ bpr = bytesPerRow(first);
2799
+ prevRow = new Uint8Array(bpr);
2800
+ rowBuf = new Uint8Array(1 + bpr);
2801
+ await writer.writeRaw(PNG_SIGNATURE);
2802
+ await writer.writeRaw(ihdrChunk(first, 1));
2803
+ for (const c of parsed.carried)
2804
+ await writer.writeChunk(c.type, c.data);
2805
+ encoder = new RowEncoder(writer, bpr, bpp);
2806
+ } else {
2807
+ assertCompatible(first, parsed.header, firstCarried, parsed.carried, index);
2808
+ }
2809
+ const enc = encoder;
2810
+ const raw = rowBuf.subarray(1);
2811
+ prevRow.fill(0);
2812
+ let rows = 0;
2813
+ let filled = 0;
2814
+ for await (const chunk of inflateBandIdat(band, parsed.idat, at)) {
2815
+ let off = 0;
2816
+ while (off < chunk.length) {
2817
+ const take = Math.min(rowBuf.length - filled, chunk.length - off);
2818
+ rowBuf.set(chunk.subarray(off, off + take), filled);
2819
+ filled += take;
2820
+ off += take;
2821
+ if (filled !== rowBuf.length)
2822
+ continue;
2823
+ if (rows >= parsed.header.height) {
2824
+ throw new PngStitchError(`${at}: IDAT holds more scanlines than its IHDR height of ${parsed.header.height}`);
2825
+ }
2826
+ unfilterRow(rowBuf[0], raw, prevRow, bpp, at);
2827
+ await enc.addRow(raw);
2828
+ prevRow.set(raw);
2829
+ rows += 1;
2830
+ filled = 0;
2831
+ }
2832
+ }
2833
+ if (filled !== 0 || rows !== parsed.header.height) {
2834
+ throw new PngStitchError(`${at}: IDAT decoded to ${rows} complete scanlines${filled > 0 ? ` plus ${filled} trailing bytes` : ""}, but its IHDR declares ${parsed.header.height}`);
2835
+ }
2836
+ totalRows += parsed.header.height;
2837
+ if (totalRows > 2147483647)
2838
+ throw new PngStitchError(`stitched height would be ${totalRows}, past PNG's 2^31-1 limit`);
2839
+ }
2840
+ if (!first || !encoder)
2841
+ throw new PngStitchError("no bands to stitch");
2842
+ await encoder.finish();
2843
+ await writer.writeChunk("IEND", new Uint8Array(0));
2844
+ await writer.patchAt(8, ihdrChunk(first, totalRows));
2845
+ return { width: first.width, height: totalRows, bytes: writer.bytes, bands: index };
2846
+ } catch (err) {
2847
+ await encoder?.abort();
2848
+ throw err;
2849
+ }
2850
+ }
2851
+
2852
+ // src/shared-tools.ts
2133
2853
  var ARTIFACT_DIR = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
2134
2854
  var STATE_DIR = process.env.CDP_STATE_DIR ?? "/tmp/cdp-toolkit";
2135
2855
 
@@ -2274,8 +2994,8 @@ async function closePage(driver, args) {
2274
2994
  return { closed: p.id, success: true, ...released.released ? { leaseReleased: true } : {} };
2275
2995
  }
2276
2996
  async function writeSelectedFile(id) {
2277
- await mkdir3(STATE_DIR, { recursive: true });
2278
- await writeFile3(join3(STATE_DIR, "selected"), id, "utf8");
2997
+ await mkdir5(STATE_DIR, { recursive: true });
2998
+ await writeFile4(join4(STATE_DIR, "selected"), id, "utf8");
2279
2999
  }
2280
3000
  async function selectPage(driver, args) {
2281
3001
  if (args.target === undefined || args.target === "")
@@ -2339,10 +3059,10 @@ function serializeForSink(value) {
2339
3059
  return json ?? "null";
2340
3060
  }
2341
3061
  async function writeJsonSink(savePath, value) {
2342
- const path = savePath.startsWith("/") ? savePath : join3(ARTIFACT_DIR, savePath);
2343
- await mkdir3(dirname(path), { recursive: true });
3062
+ const path = savePath.startsWith("/") ? savePath : join4(ARTIFACT_DIR, savePath);
3063
+ await mkdir5(dirname2(path), { recursive: true });
2344
3064
  const json = serializeForSink(value);
2345
- await writeFile3(path, json, "utf8");
3065
+ await writeFile4(path, json, "utf8");
2346
3066
  return { path, bytes: Buffer.byteLength(json, "utf8") };
2347
3067
  }
2348
3068
  function sinkRequested(savePath) {
@@ -2646,30 +3366,166 @@ async function uploadFile(driver, args) {
2646
3366
  function stamp() {
2647
3367
  return new Date(Date.now()).toISOString().replace(/[:.]/g, "-");
2648
3368
  }
3369
+ var MAX_SCREENSHOT_SCALE = 8;
3370
+ function resolveScreenshotScale(scale, capabilities) {
3371
+ if (scale === undefined)
3372
+ return 1;
3373
+ if (typeof scale !== "number" || !Number.isFinite(scale) || scale <= 0 || scale > MAX_SCREENSHOT_SCALE) {
3374
+ throw new SharedToolError(`take_screenshot: 'scale' must be a finite number greater than 0 and at most ${MAX_SCREENSHOT_SCALE} (default 1)`);
3375
+ }
3376
+ if (scale !== 1 && !capabilities.has("screenshot.scale")) {
3377
+ throw new SharedToolError("take_screenshot: 'scale' is not supported by this backend (WebDriver BiDi's browsingContext.captureScreenshot has no scale parameter). " + `Call emulate with deviceScaleFactor:${scale} first and capture at scale 1, or use --browser chrome for a per-capture scale.`);
3378
+ }
3379
+ return scale;
3380
+ }
3381
+ var MAX_RENDER_SIZE_PX = 16384;
3382
+ function resolveRenderSize(renderWidth, renderHeight, capabilities) {
3383
+ if (renderWidth === undefined && renderHeight === undefined)
3384
+ return;
3385
+ if (renderWidth === undefined || renderHeight === undefined) {
3386
+ throw new SharedToolError("take_screenshot: 'renderWidth' and 'renderHeight' are required together (a viewport is two numbers; " + `got ${renderWidth === undefined ? "only renderHeight" : "only renderWidth"})`);
3387
+ }
3388
+ for (const [name, value] of [["renderWidth", renderWidth], ["renderHeight", renderHeight]]) {
3389
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > MAX_RENDER_SIZE_PX) {
3390
+ throw new SharedToolError(`take_screenshot: '${name}' must be an integer between 1 and ${MAX_RENDER_SIZE_PX} css px (got ${JSON.stringify(value)})`);
3391
+ }
3392
+ }
3393
+ if (!capabilities.has("screenshot.renderSize")) {
3394
+ throw new SharedToolError("take_screenshot: 'renderWidth'/'renderHeight' are not supported by this backend (it cannot emulate a viewport for a capture). " + `Call emulate with width:${renderWidth}, height:${renderHeight} first, capture, then restore the viewport yourself.`);
3395
+ }
3396
+ return { width: renderWidth, height: renderHeight };
3397
+ }
3398
+ function resolveTileMode(tile, capabilities, format, returnBase64) {
3399
+ if (tile === undefined)
3400
+ return;
3401
+ if (typeof tile !== "boolean")
3402
+ throw new SharedToolError(`take_screenshot: 'tile' must be true, false, or omitted for automatic (got ${JSON.stringify(tile)})`);
3403
+ if (!tile)
3404
+ return false;
3405
+ if (!capabilities.has("screenshot.tile")) {
3406
+ throw new SharedToolError("take_screenshot: 'tile' is not supported by this backend (band tiling is declared only where the band arithmetic was measured against a real browser; " + "Firefox was not driven for it). Capture a smaller region or a single element, or use --browser chrome for a tiled capture.");
3407
+ }
3408
+ if (format !== "png")
3409
+ throw tiledCaptureRefusal("format");
3410
+ if (returnBase64)
3411
+ throw tiledCaptureRefusal("base64");
3412
+ return true;
3413
+ }
3414
+ function tiledCaptureRefusal(reason, banded) {
3415
+ const because = banded ? ` This capture needs ${banded.bandCount} bands (${banded.clip.width}x${banded.clip.height} css px is past what one image can encode).` : "";
3416
+ if (reason === "format") {
3417
+ return new SharedToolError("take_screenshot: a tiled capture is PNG-only — bands are stitched by concatenating PNG scanlines, and there is no lossless " + `JPEG equivalent to concatenate.${because} Pass format:"png", or lower scale / narrow the region until one JPEG fits.`);
3418
+ }
3419
+ return new SharedToolError("take_screenshot: 'returnBase64' cannot be combined with a tiled capture — a tiled image runs to hundreds of megabytes and base64 " + `inflates it by a third, into the caller's response.${because} Read the file at the returned path instead, or capture a region small enough for one shot.`);
3420
+ }
3421
+ function pngPixelSize(b, view) {
3422
+ if (b.length < 24)
3423
+ return;
3424
+ if (view.getUint32(0) !== 2303741511 || view.getUint32(4) !== 218765834)
3425
+ return;
3426
+ if (String.fromCharCode(b[12], b[13], b[14], b[15]) !== "IHDR")
3427
+ return;
3428
+ return { width: view.getUint32(16), height: view.getUint32(20) };
3429
+ }
3430
+ var JPEG_SOF = new Set([192, 193, 194, 195, 197, 198, 199, 201, 202, 203, 205, 206, 207]);
3431
+ function jpegPixelSize(b, view) {
3432
+ if (b.length < 4 || b[0] !== 255 || b[1] !== 216)
3433
+ return;
3434
+ let i = 2;
3435
+ while (i + 3 < b.length) {
3436
+ if (b[i] !== 255) {
3437
+ i += 1;
3438
+ continue;
3439
+ }
3440
+ const marker = b[i + 1];
3441
+ if (marker === 255) {
3442
+ i += 1;
3443
+ continue;
3444
+ }
3445
+ if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215) {
3446
+ i += 2;
3447
+ continue;
3448
+ }
3449
+ const segLen = view.getUint16(i + 2);
3450
+ if (JPEG_SOF.has(marker)) {
3451
+ if (i + 9 > b.length)
3452
+ return;
3453
+ return { width: view.getUint16(i + 7), height: view.getUint16(i + 5) };
3454
+ }
3455
+ if (segLen < 2)
3456
+ return;
3457
+ i += 2 + segLen;
3458
+ }
3459
+ return;
3460
+ }
3461
+ function imagePixelSize(bytes) {
3462
+ if (bytes.byteLength < 4)
3463
+ return;
3464
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
3465
+ return pngPixelSize(bytes, view) ?? jpegPixelSize(bytes, view);
3466
+ }
2649
3467
  async function takeScreenshot(driver, args = {}) {
2650
3468
  if (args.uid !== undefined && args.uid !== "" && args.selector)
2651
3469
  throw new SharedToolError("provide exactly one of uid or selector, not both");
2652
3470
  const format = args.format ?? "png";
3471
+ const scale = resolveScreenshotScale(args.scale, driver.capabilities);
3472
+ const renderSize = resolveRenderSize(args.renderWidth, args.renderHeight, driver.capabilities);
3473
+ const tile = resolveTileMode(args.tile, driver.capabilities, format, args.returnBase64);
2653
3474
  return withPage2(driver, args.target, async (page) => {
2654
3475
  const clip = args.uid !== undefined || args.selector ? locatorOf(args) : undefined;
2655
- const { data, format: outFormat } = await page.screenshot({ format, quality: args.quality, fullPage: args.fullPage, ...clip ? { clip } : {} });
2656
- if (data.byteLength === 0)
2657
- throw new SharedToolError("captureScreenshot returned empty data");
2658
- await mkdir3(ARTIFACT_DIR, { recursive: true });
2659
- const ext = outFormat === "jpeg" ? "jpg" : "png";
2660
- const path = args.savePath ?? join3(ARTIFACT_DIR, `screenshot-${page.info.id.slice(0, 8)}-${stamp()}.${ext}`);
2661
- await writeFile3(path, Buffer.from(data));
2662
- const result = {
2663
- path,
2664
- bytes: data.byteLength,
2665
- format: outFormat,
2666
- target: target3(page.info)
3476
+ const ext = format === "jpeg" ? "jpg" : "png";
3477
+ const path = args.savePath ?? join4(ARTIFACT_DIR, `screenshot-${page.info.id.slice(0, 8)}-${stamp()}.${ext}`);
3478
+ const stitch = async (banded) => {
3479
+ if (format !== "png")
3480
+ throw tiledCaptureRefusal("format", banded);
3481
+ if (args.returnBase64)
3482
+ throw tiledCaptureRefusal("base64", banded);
3483
+ return stitchPngBandsToFile(banded.bands, path);
2667
3484
  };
2668
- if (args.returnBase64)
2669
- result.base64 = Buffer.from(data).toString("base64");
3485
+ const shot = await page.screenshot({
3486
+ format,
3487
+ quality: args.quality,
3488
+ fullPage: args.fullPage,
3489
+ scale,
3490
+ ...clip ? { clip } : {},
3491
+ ...renderSize ? { renderWidth: renderSize.width, renderHeight: renderSize.height } : {},
3492
+ ...tile !== undefined ? { tile } : {}
3493
+ }, stitch);
3494
+ const result = isTiledCapture(shot) ? {
3495
+ path,
3496
+ bytes: shot.consumed.bytes,
3497
+ format: "png",
3498
+ target: target3(page.info),
3499
+ scale,
3500
+ width: shot.consumed.width,
3501
+ height: shot.consumed.height,
3502
+ tiled: true,
3503
+ bands: shot.consumed.bands
3504
+ } : await writeOneShot(shot.data, shot.format, path, target3(page.info), scale, args.returnBase64);
3505
+ if (renderSize && shot.renderSize) {
3506
+ result.renderSize = shot.renderSize;
3507
+ result.renderRestored = shot.renderRestored ?? false;
3508
+ if (shot.renderRestoreError !== undefined)
3509
+ result.renderRestoreError = shot.renderRestoreError;
3510
+ }
2670
3511
  return result;
2671
3512
  });
2672
3513
  }
3514
+ async function writeOneShot(data, outFormat, path, target, scale, returnBase64) {
3515
+ if (data.byteLength === 0)
3516
+ throw new SharedToolError("captureScreenshot returned empty data");
3517
+ await mkdir5(dirname2(path), { recursive: true });
3518
+ await writeFile4(path, Buffer.from(data));
3519
+ const size = imagePixelSize(data);
3520
+ const result = { path, bytes: data.byteLength, format: outFormat, target, scale };
3521
+ if (size) {
3522
+ result.width = size.width;
3523
+ result.height = size.height;
3524
+ }
3525
+ if (returnBase64)
3526
+ result.base64 = Buffer.from(data).toString("base64");
3527
+ return result;
3528
+ }
2673
3529
  async function emulate(driver, args = {}) {
2674
3530
  return withPage2(driver, args.target, async (page) => {
2675
3531
  if (args.clearOverrides) {
@@ -2840,10 +3696,10 @@ var LEASE_TOOLS = {
2840
3696
  };
2841
3697
 
2842
3698
  // src/tools/console.ts
2843
- import { readFile as readFile3 } from "node:fs/promises";
3699
+ import { readFile as readFile4 } from "node:fs/promises";
2844
3700
 
2845
3701
  // src/tools/recorder.ts
2846
- import { mkdir as mkdir4, appendFile, writeFile as writeFile4, copyFile } from "node:fs/promises";
3702
+ import { mkdir as mkdir6, appendFile, writeFile as writeFile5, copyFile } from "node:fs/promises";
2847
3703
  var ARTIFACT_DIR2 = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
2848
3704
  function recFile(targetId) {
2849
3705
  return `${ARTIFACT_DIR2}/rec-${targetId}.jsonl`;
@@ -2889,11 +3745,11 @@ var CONSOLE_EVENTS = ["Runtime.consoleAPICalled", "Runtime.exceptionThrown", "Lo
2889
3745
  async function startRecorder(target, opts = {}) {
2890
3746
  const network = opts.network ?? true;
2891
3747
  const consoleCap = opts.console ?? true;
2892
- await mkdir4(ARTIFACT_DIR2, { recursive: true });
3748
+ await mkdir6(ARTIFACT_DIR2, { recursive: true });
2893
3749
  const { conn, target: resolved } = await openPage(target, { timeoutMs: opts.timeoutMs });
2894
3750
  const file = opts.file ?? recFile(resolved.id);
2895
3751
  if (opts.truncate) {
2896
- await writeFile4(file, "");
3752
+ await writeFile5(file, "");
2897
3753
  }
2898
3754
  let chain = Promise.resolve();
2899
3755
  let dropped = 0;
@@ -3039,7 +3895,7 @@ function lineToEntry(line, index) {
3039
3895
  async function readConsoleEntries(file) {
3040
3896
  let raw;
3041
3897
  try {
3042
- raw = await readFile3(file, "utf8");
3898
+ raw = await readFile4(file, "utf8");
3043
3899
  } catch {
3044
3900
  return [];
3045
3901
  }
@@ -3096,7 +3952,7 @@ async function getConsoleMessage(args = {}) {
3096
3952
  }
3097
3953
 
3098
3954
  // src/tools/network.ts
3099
- import { readFile as readFile4 } from "node:fs/promises";
3955
+ import { readFile as readFile5 } from "node:fs/promises";
3100
3956
  var DEFAULT_CAPTURE_MS2 = 2500;
3101
3957
  function asHeaders(v) {
3102
3958
  if (!v || typeof v !== "object")
@@ -3163,7 +4019,7 @@ function correlate(lines) {
3163
4019
  async function readNetworkRequests(file) {
3164
4020
  let raw;
3165
4021
  try {
3166
- raw = await readFile4(file, "utf8");
4022
+ raw = await readFile5(file, "utf8");
3167
4023
  } catch {
3168
4024
  return [];
3169
4025
  }
@@ -3247,8 +4103,8 @@ async function getNetworkRequest(args = {}) {
3247
4103
  }
3248
4104
 
3249
4105
  // src/tools/performance.ts
3250
- import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5, stat, unlink as unlink3 } from "node:fs/promises";
3251
- import { join as join4 } from "node:path";
4106
+ import { mkdir as mkdir7, readFile as readFile6, writeFile as writeFile6, stat, unlink as unlink4 } from "node:fs/promises";
4107
+ import { join as join5 } from "node:path";
3252
4108
  var ARTIFACT_DIR3 = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
3253
4109
  var STATE_DIR2 = process.env.CDP_STATE_DIR ?? "/tmp/cdp-toolkit";
3254
4110
  var DEFAULT_CATEGORIES = [
@@ -3266,7 +4122,7 @@ function shortId(id) {
3266
4122
  return id.slice(0, 8);
3267
4123
  }
3268
4124
  function stateFilePath() {
3269
- return join4(STATE_DIR2, "trace-state.json");
4125
+ return join5(STATE_DIR2, "trace-state.json");
3270
4126
  }
3271
4127
  function bufferTraceData(conn, sink) {
3272
4128
  return conn.on("Tracing.dataCollected", (params) => {
@@ -3288,10 +4144,10 @@ async function endTrace(conn, sink, timeoutMs = 30000) {
3288
4144
  return sink;
3289
4145
  }
3290
4146
  async function writeTrace(events, targetId) {
3291
- await mkdir5(ARTIFACT_DIR3, { recursive: true });
3292
- const path = join4(ARTIFACT_DIR3, `trace-${shortId(targetId)}-${stamp2()}.json`);
4147
+ await mkdir7(ARTIFACT_DIR3, { recursive: true });
4148
+ const path = join5(ARTIFACT_DIR3, `trace-${shortId(targetId)}-${stamp2()}.json`);
3293
4149
  const body = JSON.stringify({ traceEvents: events, metadata: { source: "cdp-toolkit" } });
3294
- await writeFile5(path, body, "utf8");
4150
+ await writeFile6(path, body, "utf8");
3295
4151
  const { size } = await stat(path);
3296
4152
  return { path, bytes: size };
3297
4153
  }
@@ -3311,7 +4167,7 @@ async function performanceStartTrace(args = {}) {
3311
4167
  throw err;
3312
4168
  }
3313
4169
  liveTraces.set(target.id, { conn, target, events, startedAt: Date.now() });
3314
- await mkdir5(STATE_DIR2, { recursive: true });
4170
+ await mkdir7(STATE_DIR2, { recursive: true });
3315
4171
  const state = {
3316
4172
  targetId: target.id,
3317
4173
  url: target.url,
@@ -3319,7 +4175,7 @@ async function performanceStartTrace(args = {}) {
3319
4175
  pid: process.pid,
3320
4176
  categories
3321
4177
  };
3322
- await writeFile5(stateFilePath(), JSON.stringify(state), "utf8");
4178
+ await writeFile6(stateFilePath(), JSON.stringify(state), "utf8");
3323
4179
  return {
3324
4180
  target: { id: target.id, url: target.url, title: target.title },
3325
4181
  categories,
@@ -3332,7 +4188,7 @@ async function performanceStopTrace(args = {}) {
3332
4188
  if (!entry) {
3333
4189
  let hint = "";
3334
4190
  try {
3335
- const raw = await readFile5(stateFilePath(), "utf8");
4191
+ const raw = await readFile6(stateFilePath(), "utf8");
3336
4192
  const state = JSON.parse(raw);
3337
4193
  hint = state.pid === process.pid ? ` (state file lists target ${shortId(state.targetId)} but its live connection is gone)` : ` (trace was started by a DIFFERENT process pid=${state.pid}; live recordings cannot cross process boundaries)`;
3338
4194
  } catch {}
@@ -3344,7 +4200,7 @@ async function performanceStopTrace(args = {}) {
3344
4200
  } finally {
3345
4201
  conn.close();
3346
4202
  liveTraces.delete(target.id);
3347
- await unlink3(stateFilePath()).catch(() => {});
4203
+ await unlink4(stateFilePath()).catch(() => {});
3348
4204
  }
3349
4205
  const written = await writeTrace(events, target.id);
3350
4206
  const metrics = analyzeEvents(events);
@@ -3462,7 +4318,7 @@ async function performanceAnalyzeInsight(args = {}) {
3462
4318
  }
3463
4319
  let raw;
3464
4320
  try {
3465
- raw = await readFile5(args.tracePath, "utf8");
4321
+ raw = await readFile6(args.tracePath, "utf8");
3466
4322
  } catch (err) {
3467
4323
  throw new CdpError(`cannot read trace file '${args.tracePath}': ${err.message}`);
3468
4324
  }
@@ -3480,8 +4336,8 @@ async function performanceAnalyzeInsight(args = {}) {
3480
4336
 
3481
4337
  // src/tools/screencast.ts
3482
4338
  import { spawn } from "node:child_process";
3483
- import { mkdir as mkdir6, rm, stat as stat2, writeFile as writeFile6 } from "node:fs/promises";
3484
- import { join as join5 } from "node:path";
4339
+ import { mkdir as mkdir8, rm as rm2, stat as stat2, writeFile as writeFile7 } from "node:fs/promises";
4340
+ import { join as join6 } from "node:path";
3485
4341
  var ARTIFACT_DIR4 = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
3486
4342
  var FFMPEG_BIN = process.env.CDP_FFMPEG ?? "ffmpeg";
3487
4343
  function frameDurationsMs(ledger, stopWallclockMs) {
@@ -3537,7 +4393,7 @@ function pngSize(buf) {
3537
4393
  return;
3538
4394
  return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
3539
4395
  }
3540
- var JPEG_SOF = new Set([192, 193, 194, 195, 197, 198, 199, 201, 202, 203, 205, 206, 207]);
4396
+ var JPEG_SOF2 = new Set([192, 193, 194, 195, 197, 198, 199, 201, 202, 203, 205, 206, 207]);
3541
4397
  function jpegSize(buf) {
3542
4398
  if (buf.length < 4 || buf[0] !== 255 || buf[1] !== 216)
3543
4399
  return;
@@ -3559,7 +4415,7 @@ function jpegSize(buf) {
3559
4415
  if (i + 3 >= buf.length)
3560
4416
  return;
3561
4417
  const segLen = buf.readUInt16BE(i + 2);
3562
- if (JPEG_SOF.has(marker)) {
4418
+ if (JPEG_SOF2.has(marker)) {
3563
4419
  if (i + 9 > buf.length)
3564
4420
  return;
3565
4421
  return { width: buf.readUInt16BE(i + 7), height: buf.readUInt16BE(i + 5) };
@@ -3702,9 +4558,9 @@ async function startScreenRecording(args = {}) {
3702
4558
  conn.close();
3703
4559
  throw err;
3704
4560
  }
3705
- const spoolDir = join5(ARTIFACT_DIR4, `screencast-${shortId2(target.id)}-${stamp3()}`);
4561
+ const spoolDir = join6(ARTIFACT_DIR4, `screencast-${shortId2(target.id)}-${stamp3()}`);
3706
4562
  try {
3707
- await mkdir6(spoolDir, { recursive: true });
4563
+ await mkdir8(spoolDir, { recursive: true });
3708
4564
  } catch (err) {
3709
4565
  conn.close();
3710
4566
  throw new CdpError(`cannot create the screencast spool directory '${spoolDir}': ${err.message}`);
@@ -3745,7 +4601,7 @@ async function startScreenRecording(args = {}) {
3745
4601
  seq += 1;
3746
4602
  const file = `frame-${String(seq).padStart(6, "0")}.${ext}`;
3747
4603
  rec.ledger.push({ file, timestamp, width: size.width, height: size.height });
3748
- rec.writes = rec.writes.then(() => writeFile6(join5(spoolDir, file), buf)).catch(() => {
4604
+ rec.writes = rec.writes.then(() => writeFile7(join6(spoolDir, file), buf)).catch(() => {
3749
4605
  rec.dropped += 1;
3750
4606
  rec.unwritten.add(file);
3751
4607
  });
@@ -3767,7 +4623,7 @@ async function startScreenRecording(args = {}) {
3767
4623
  } catch (err) {
3768
4624
  rec.off();
3769
4625
  conn.close();
3770
- await rm(spoolDir, { recursive: true, force: true }).catch(() => {});
4626
+ await rm2(spoolDir, { recursive: true, force: true }).catch(() => {});
3771
4627
  throw err;
3772
4628
  }
3773
4629
  liveRecordings.set(target.id, rec);
@@ -3805,14 +4661,14 @@ async function stopScreenRecording(args = {}) {
3805
4661
  liveRecordings.delete(rec.target.id);
3806
4662
  const ledger = rec.unwritten.size ? rec.ledger.filter((entry) => !rec.unwritten.has(entry.file)) : rec.ledger;
3807
4663
  if (!ledger.length) {
3808
- await rm(rec.spoolDir, { recursive: true, force: true }).catch(() => {});
4664
+ await rm2(rec.spoolDir, { recursive: true, force: true }).catch(() => {});
3809
4665
  throw new CdpError(`screen recording captured 0 frames for target ${shortId2(rec.target.id)}. Chrome emits a screencast frame only on repaint: a fully backgrounded or occluded tab may never paint. Retry with bringToFront:true, or drive the page while recording.`);
3810
4666
  }
3811
- await mkdir6(ARTIFACT_DIR4, { recursive: true });
3812
- const outPath = args.savePath ?? join5(ARTIFACT_DIR4, `screen-recording-${shortId2(rec.target.id)}-${stamp3()}.mp4`);
4667
+ await mkdir8(ARTIFACT_DIR4, { recursive: true });
4668
+ const outPath = args.savePath ?? join6(ARTIFACT_DIR4, `screen-recording-${shortId2(rec.target.id)}-${stamp3()}.mp4`);
3813
4669
  const frames = coalesceToConcatGrid(ledger, stoppedAt);
3814
- const manifestPath = join5(rec.spoolDir, "frames.ffconcat");
3815
- await writeFile6(manifestPath, buildConcatManifest(frames), "utf8");
4670
+ const manifestPath = join6(rec.spoolDir, "frames.ffconcat");
4671
+ await writeFile7(manifestPath, buildConcatManifest(frames), "utf8");
3816
4672
  const durationMs = frames.reduce((total, f) => total + f.durationMs, 0);
3817
4673
  const { width, height } = evenDimensions(ledger[0].width, ledger[0].height);
3818
4674
  let res;
@@ -3826,7 +4682,7 @@ async function stopScreenRecording(args = {}) {
3826
4682
  ffmpeg said: ${res.stderr.trim().slice(0, 800)}`);
3827
4683
  }
3828
4684
  const { size } = await stat2(outPath);
3829
- await rm(rec.spoolDir, { recursive: true, force: true }).catch(() => {});
4685
+ await rm2(rec.spoolDir, { recursive: true, force: true }).catch(() => {});
3830
4686
  return {
3831
4687
  path: outPath,
3832
4688
  bytes: size,
@@ -3843,8 +4699,8 @@ ffmpeg said: ${res.stderr.trim().slice(0, 800)}`);
3843
4699
  }
3844
4700
 
3845
4701
  // src/tools/heap.ts
3846
- import { mkdir as mkdir7, writeFile as writeFile7 } from "node:fs/promises";
3847
- import { join as join6 } from "node:path";
4702
+ import { mkdir as mkdir9, writeFile as writeFile8 } from "node:fs/promises";
4703
+ import { join as join7 } from "node:path";
3848
4704
  var ARTIFACT_DIR5 = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
3849
4705
  function stamp4(targetId) {
3850
4706
  const short = targetId.slice(0, 8);
@@ -3869,9 +4725,9 @@ async function takeHeapsnapshot(args = {}) {
3869
4725
  if (snapshot.length === 0) {
3870
4726
  throw new Error("HeapProfiler.takeHeapSnapshot produced no chunks");
3871
4727
  }
3872
- await mkdir7(ARTIFACT_DIR5, { recursive: true });
3873
- const path = args.savePath ? args.savePath.startsWith("/") ? args.savePath : join6(ARTIFACT_DIR5, args.savePath) : join6(ARTIFACT_DIR5, `take_heapsnapshot-${stamp4(target.id)}.heapsnapshot`);
3874
- await writeFile7(path, snapshot, "utf8");
4728
+ await mkdir9(ARTIFACT_DIR5, { recursive: true });
4729
+ const path = args.savePath ? args.savePath.startsWith("/") ? args.savePath : join7(ARTIFACT_DIR5, args.savePath) : join7(ARTIFACT_DIR5, `take_heapsnapshot-${stamp4(target.id)}.heapsnapshot`);
4730
+ await writeFile8(path, snapshot, "utf8");
3875
4731
  const bytes = Buffer.byteLength(snapshot, "utf8");
3876
4732
  return {
3877
4733
  path,
@@ -3884,7 +4740,7 @@ async function takeHeapsnapshot(args = {}) {
3884
4740
 
3885
4741
  // src/tools/lighthouse.ts
3886
4742
  import { spawn as spawn2 } from "node:child_process";
3887
- import { mkdir as mkdir8, readFile as readFile6, stat as stat3 } from "node:fs/promises";
4743
+ import { mkdir as mkdir10, readFile as readFile7, stat as stat3 } from "node:fs/promises";
3888
4744
  var ARTIFACT_DIR6 = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
3889
4745
  function cdpPort() {
3890
4746
  const base = process.env.CDP_BASE ?? "http://127.0.0.1:9222";
@@ -3945,7 +4801,7 @@ async function lighthouseAudit(args) {
3945
4801
  const url = args.url.trim();
3946
4802
  const timeoutMs = args.timeoutMs ?? 120000;
3947
4803
  const port = cdpPort();
3948
- await mkdir8(ARTIFACT_DIR6, { recursive: true });
4804
+ await mkdir10(ARTIFACT_DIR6, { recursive: true });
3949
4805
  const outPath = args.savePath ?? `${ARTIFACT_DIR6}/lighthouse-${shortHost(url)}-${stamp5()}.json`;
3950
4806
  const formFactor = args.formFactor ?? "desktop";
3951
4807
  const lhArgs = [
@@ -3974,7 +4830,7 @@ async function lighthouseAudit(args) {
3974
4830
  }
3975
4831
  let raw;
3976
4832
  try {
3977
- raw = await readFile6(outPath, "utf8");
4833
+ raw = await readFile7(outPath, "utf8");
3978
4834
  } catch {
3979
4835
  raw = undefined;
3980
4836
  }
@@ -4061,14 +4917,14 @@ async function dispatchMouse(args) {
4061
4917
  }
4062
4918
 
4063
4919
  // src/tools/downloads.ts
4064
- import { readdir as readdir3, rename, stat as stat4 } from "node:fs/promises";
4065
- import { basename, extname, join as join8 } from "node:path";
4920
+ import { readdir as readdir3, rename as rename2, stat as stat4 } from "node:fs/promises";
4921
+ import { basename, extname, join as join9 } from "node:path";
4066
4922
 
4067
4923
  // src/tools/browser-session.ts
4068
- import { mkdir as mkdir9 } from "node:fs/promises";
4069
- import { join as join7 } from "node:path";
4924
+ import { mkdir as mkdir11 } from "node:fs/promises";
4925
+ import { join as join8 } from "node:path";
4070
4926
  var ARTIFACT_DIR7 = process.env.CDP_ARTIFACT_DIR ?? "/tmp/cdp-toolkit";
4071
- var DOWNLOADS_DIR = join7(ARTIFACT_DIR7, "downloads");
4927
+ var DOWNLOADS_DIR = join8(ARTIFACT_DIR7, "downloads");
4072
4928
  var DOWNLOAD_BUFFER_LIMIT = 50;
4073
4929
  var session;
4074
4930
  function isDisconnect(e) {
@@ -4140,7 +4996,7 @@ async function browserSend(method, params = {}) {
4140
4996
  }
4141
4997
  }
4142
4998
  async function armDownloadsAt(path) {
4143
- await mkdir9(path, { recursive: true });
4999
+ await mkdir11(path, { recursive: true });
4144
5000
  const s = await getBrowserSession();
4145
5001
  await s.conn.send("Browser.setDownloadBehavior", { behavior: "allowAndName", downloadPath: path, eventsEnabled: true });
4146
5002
  s.armedPath = path;
@@ -4230,8 +5086,8 @@ async function waitForDownload(args = {}) {
4230
5086
  const taken = new Set(await readdir3(DOWNLOADS_DIR).catch(() => []));
4231
5087
  taken.delete(basename(rec.filePath));
4232
5088
  const finalName = nextFreeName(suggestedFilename, taken);
4233
- const path = join8(DOWNLOADS_DIR, finalName);
4234
- await rename(rec.filePath, path);
5089
+ const path = join9(DOWNLOADS_DIR, finalName);
5090
+ await rename2(rec.filePath, path);
4235
5091
  const bytes = (await stat4(path)).size;
4236
5092
  return { path, suggestedFilename, bytes, ...rec.url ? { url: rec.url } : {}, target };
4237
5093
  }