@agenticmail/enterprise 0.5.77 → 0.5.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/dist/chunk-7RNT4O5T.js +15198 -0
  2. package/dist/chunk-AGFOJCSB.js +2191 -0
  3. package/dist/chunk-CYABMD5B.js +2191 -0
  4. package/dist/chunk-F4GSFCM3.js +898 -0
  5. package/dist/chunk-GINZ56GG.js +15035 -0
  6. package/dist/chunk-NRKB2KGD.js +898 -0
  7. package/dist/chunk-PZA7YOJE.js +898 -0
  8. package/dist/chunk-Q3V7VZFQ.js +2191 -0
  9. package/dist/chunk-RRFB6G6M.js +15198 -0
  10. package/dist/chunk-VX3VFMVB.js +409 -0
  11. package/dist/cli.js +1 -1
  12. package/dist/dashboard/pages/agent-detail.js +491 -2
  13. package/dist/index.js +4 -3
  14. package/dist/pw-ai-KPETTB25.js +2212 -0
  15. package/dist/routes-2T2ZNH3D.js +6642 -0
  16. package/dist/routes-PDHMCIXU.js +6676 -0
  17. package/dist/runtime-5ZJYB5PY.js +47 -0
  18. package/dist/runtime-7HW4GX5L.js +48 -0
  19. package/dist/runtime-XXDCZZIK.js +48 -0
  20. package/dist/server-FMP4BFGW.js +12 -0
  21. package/dist/server-JRHDUNII.js +12 -0
  22. package/dist/server-QPIMKFK4.js +12 -0
  23. package/dist/setup-NPFIX7LF.js +20 -0
  24. package/dist/setup-O5FPRLK4.js +20 -0
  25. package/dist/setup-S4Z4PPIJ.js +20 -0
  26. package/package.json +15 -2
  27. package/src/agent-tools/common.ts +25 -0
  28. package/src/agent-tools/index.ts +3 -0
  29. package/src/agent-tools/schema/typebox.ts +25 -0
  30. package/src/agent-tools/tools/browser-tool.schema.ts +112 -0
  31. package/src/agent-tools/tools/browser-tool.ts +388 -0
  32. package/src/agent-tools/tools/gateway.ts +126 -0
  33. package/src/agent-tools/tools/nodes-utils.ts +80 -0
  34. package/src/browser/bridge-auth-registry.ts +34 -0
  35. package/src/browser/bridge-server.ts +93 -0
  36. package/src/browser/cdp.helpers.ts +180 -0
  37. package/src/browser/cdp.ts +466 -0
  38. package/src/browser/chrome.executables.ts +625 -0
  39. package/src/browser/chrome.profile-decoration.ts +198 -0
  40. package/src/browser/chrome.ts +349 -0
  41. package/src/browser/client-actions-core.ts +259 -0
  42. package/src/browser/client-actions-observe.ts +184 -0
  43. package/src/browser/client-actions-state.ts +284 -0
  44. package/src/browser/client-actions-types.ts +16 -0
  45. package/src/browser/client-actions-url.ts +11 -0
  46. package/src/browser/client-actions.ts +4 -0
  47. package/src/browser/client-fetch.ts +253 -0
  48. package/src/browser/client.ts +337 -0
  49. package/src/browser/config.ts +296 -0
  50. package/src/browser/constants.ts +8 -0
  51. package/src/browser/control-auth.ts +94 -0
  52. package/src/browser/control-service.ts +81 -0
  53. package/src/browser/csrf.ts +87 -0
  54. package/src/browser/enterprise-compat.ts +518 -0
  55. package/src/browser/extension-relay.ts +834 -0
  56. package/src/browser/http-auth.ts +63 -0
  57. package/src/browser/navigation-guard.ts +50 -0
  58. package/src/browser/paths.ts +49 -0
  59. package/src/browser/profiles-service.ts +187 -0
  60. package/src/browser/profiles.ts +113 -0
  61. package/src/browser/proxy-files.ts +41 -0
  62. package/src/browser/pw-ai-module.ts +52 -0
  63. package/src/browser/pw-ai-state.ts +9 -0
  64. package/src/browser/pw-ai.ts +65 -0
  65. package/src/browser/pw-role-snapshot.ts +434 -0
  66. package/src/browser/pw-session.ts +810 -0
  67. package/src/browser/pw-tools-core.activity.ts +68 -0
  68. package/src/browser/pw-tools-core.downloads.ts +281 -0
  69. package/src/browser/pw-tools-core.interactions.ts +646 -0
  70. package/src/browser/pw-tools-core.responses.ts +124 -0
  71. package/src/browser/pw-tools-core.shared.ts +70 -0
  72. package/src/browser/pw-tools-core.snapshot.ts +213 -0
  73. package/src/browser/pw-tools-core.state.ts +209 -0
  74. package/src/browser/pw-tools-core.storage.ts +128 -0
  75. package/src/browser/pw-tools-core.trace.ts +37 -0
  76. package/src/browser/pw-tools-core.ts +8 -0
  77. package/src/browser/resolved-config-refresh.ts +59 -0
  78. package/src/browser/routes/agent.act.shared.ts +52 -0
  79. package/src/browser/routes/agent.act.ts +575 -0
  80. package/src/browser/routes/agent.debug.ts +149 -0
  81. package/src/browser/routes/agent.shared.ts +143 -0
  82. package/src/browser/routes/agent.snapshot.ts +333 -0
  83. package/src/browser/routes/agent.storage.ts +451 -0
  84. package/src/browser/routes/agent.ts +13 -0
  85. package/src/browser/routes/basic.ts +202 -0
  86. package/src/browser/routes/dispatcher.ts +126 -0
  87. package/src/browser/routes/index.ts +11 -0
  88. package/src/browser/routes/path-output.ts +1 -0
  89. package/src/browser/routes/tabs.ts +217 -0
  90. package/src/browser/routes/types.ts +26 -0
  91. package/src/browser/routes/utils.ts +73 -0
  92. package/src/browser/screenshot.ts +54 -0
  93. package/src/browser/server-context.ts +688 -0
  94. package/src/browser/server-context.types.ts +65 -0
  95. package/src/browser/server-lifecycle.ts +48 -0
  96. package/src/browser/server-middleware.ts +37 -0
  97. package/src/browser/server.ts +110 -0
  98. package/src/browser/target-id.ts +30 -0
  99. package/src/browser/trash.ts +21 -0
  100. package/src/dashboard/pages/agent-detail.js +491 -2
  101. package/src/engine/agent-routes.ts +246 -0
  102. package/src/security/external-content.ts +299 -0
@@ -0,0 +1,126 @@
1
+ import { escapeRegExp } from "../enterprise-compat.js";
2
+ import type { BrowserRouteContext } from "../server-context.js";
3
+ import { registerBrowserRoutes } from "./index.js";
4
+ import type { BrowserRequest, BrowserResponse, BrowserRouteRegistrar } from "./types.js";
5
+
6
+ type BrowserDispatchRequest = {
7
+ method: "GET" | "POST" | "DELETE";
8
+ path: string;
9
+ query?: Record<string, unknown>;
10
+ body?: unknown;
11
+ signal?: AbortSignal;
12
+ };
13
+
14
+ type BrowserDispatchResponse = {
15
+ status: number;
16
+ body: unknown;
17
+ };
18
+
19
+ type RouteEntry = {
20
+ method: BrowserDispatchRequest["method"];
21
+ path: string;
22
+ regex: RegExp;
23
+ paramNames: string[];
24
+ handler: (req: BrowserRequest, res: BrowserResponse) => void | Promise<void>;
25
+ };
26
+
27
+ function compileRoute(path: string): { regex: RegExp; paramNames: string[] } {
28
+ const paramNames: string[] = [];
29
+ const parts = path.split("/").map((part) => {
30
+ if (part.startsWith(":")) {
31
+ const name = part.slice(1);
32
+ paramNames.push(name);
33
+ return "([^/]+)";
34
+ }
35
+ return escapeRegExp(part);
36
+ });
37
+ return { regex: new RegExp(`^${parts.join("/")}$`), paramNames };
38
+ }
39
+
40
+ function createRegistry() {
41
+ const routes: RouteEntry[] = [];
42
+ const register =
43
+ (method: RouteEntry["method"]) => (path: string, handler: RouteEntry["handler"]) => {
44
+ const { regex, paramNames } = compileRoute(path);
45
+ routes.push({ method, path, regex, paramNames, handler });
46
+ };
47
+ const router: BrowserRouteRegistrar = {
48
+ get: register("GET"),
49
+ post: register("POST"),
50
+ delete: register("DELETE"),
51
+ };
52
+ return { routes, router };
53
+ }
54
+
55
+ function normalizePath(path: string) {
56
+ if (!path) {
57
+ return "/";
58
+ }
59
+ return path.startsWith("/") ? path : `/${path}`;
60
+ }
61
+
62
+ export function createBrowserRouteDispatcher(ctx: BrowserRouteContext) {
63
+ const registry = createRegistry();
64
+ registerBrowserRoutes(registry.router, ctx);
65
+
66
+ return {
67
+ dispatch: async (req: BrowserDispatchRequest): Promise<BrowserDispatchResponse> => {
68
+ const method = req.method;
69
+ const path = normalizePath(req.path);
70
+ const query = req.query ?? {};
71
+ const body = req.body;
72
+ const signal = req.signal;
73
+
74
+ const match = registry.routes.find((route) => {
75
+ if (route.method !== method) {
76
+ return false;
77
+ }
78
+ return route.regex.test(path);
79
+ });
80
+ if (!match) {
81
+ return { status: 404, body: { error: "Not Found" } };
82
+ }
83
+
84
+ const exec = match.regex.exec(path);
85
+ const params: Record<string, string> = {};
86
+ if (exec) {
87
+ for (const [idx, name] of match.paramNames.entries()) {
88
+ const value = exec[idx + 1];
89
+ if (typeof value === "string") {
90
+ params[name] = decodeURIComponent(value);
91
+ }
92
+ }
93
+ }
94
+
95
+ let status = 200;
96
+ let payload: unknown = undefined;
97
+ const res: BrowserResponse = {
98
+ status(code) {
99
+ status = code;
100
+ return res;
101
+ },
102
+ json(bodyValue) {
103
+ payload = bodyValue;
104
+ },
105
+ };
106
+
107
+ try {
108
+ await match.handler(
109
+ {
110
+ params,
111
+ query,
112
+ body,
113
+ signal,
114
+ },
115
+ res,
116
+ );
117
+ } catch (err) {
118
+ return { status: 500, body: { error: String(err) } };
119
+ }
120
+
121
+ return { status, body: payload };
122
+ },
123
+ };
124
+ }
125
+
126
+ export type { BrowserDispatchRequest, BrowserDispatchResponse };
@@ -0,0 +1,11 @@
1
+ import type { BrowserRouteContext } from "../server-context.js";
2
+ import { registerBrowserAgentRoutes } from "./agent.js";
3
+ import { registerBrowserBasicRoutes } from "./basic.js";
4
+ import { registerBrowserTabRoutes } from "./tabs.js";
5
+ import type { BrowserRouteRegistrar } from "./types.js";
6
+
7
+ export function registerBrowserRoutes(app: BrowserRouteRegistrar, ctx: BrowserRouteContext) {
8
+ registerBrowserBasicRoutes(app, ctx);
9
+ registerBrowserTabRoutes(app, ctx);
10
+ registerBrowserAgentRoutes(app, ctx);
11
+ }
@@ -0,0 +1 @@
1
+ export * from "../paths.js";
@@ -0,0 +1,217 @@
1
+ import type { BrowserRouteContext, ProfileContext } from "../server-context.js";
2
+ import type { BrowserRequest, BrowserResponse, BrowserRouteRegistrar } from "./types.js";
3
+ import { getProfileContext, jsonError, toNumber, toStringOrEmpty } from "./utils.js";
4
+
5
+ function resolveTabsProfileContext(
6
+ req: BrowserRequest,
7
+ res: BrowserResponse,
8
+ ctx: BrowserRouteContext,
9
+ ) {
10
+ const profileCtx = getProfileContext(req, ctx);
11
+ if ("error" in profileCtx) {
12
+ jsonError(res, profileCtx.status, profileCtx.error);
13
+ return null;
14
+ }
15
+ return profileCtx;
16
+ }
17
+
18
+ function handleTabsRouteError(
19
+ ctx: BrowserRouteContext,
20
+ res: BrowserResponse,
21
+ err: unknown,
22
+ opts?: { mapTabError?: boolean },
23
+ ) {
24
+ if (opts?.mapTabError) {
25
+ const mapped = ctx.mapTabError(err);
26
+ if (mapped) {
27
+ return jsonError(res, mapped.status, mapped.message);
28
+ }
29
+ }
30
+ return jsonError(res, 500, String(err));
31
+ }
32
+
33
+ async function withTabsProfileRoute(params: {
34
+ req: BrowserRequest;
35
+ res: BrowserResponse;
36
+ ctx: BrowserRouteContext;
37
+ mapTabError?: boolean;
38
+ run: (profileCtx: ProfileContext) => Promise<void>;
39
+ }) {
40
+ const profileCtx = resolveTabsProfileContext(params.req, params.res, params.ctx);
41
+ if (!profileCtx) {
42
+ return;
43
+ }
44
+ try {
45
+ await params.run(profileCtx);
46
+ } catch (err) {
47
+ handleTabsRouteError(params.ctx, params.res, err, { mapTabError: params.mapTabError });
48
+ }
49
+ }
50
+
51
+ async function ensureBrowserRunning(profileCtx: ProfileContext, res: BrowserResponse) {
52
+ if (!(await profileCtx.isReachable(300))) {
53
+ jsonError(res, 409, "browser not running");
54
+ return false;
55
+ }
56
+ return true;
57
+ }
58
+
59
+ function resolveIndexedTab(
60
+ tabs: Awaited<ReturnType<ProfileContext["listTabs"]>>,
61
+ index: number | undefined,
62
+ ) {
63
+ return typeof index === "number" ? tabs[index] : tabs.at(0);
64
+ }
65
+
66
+ function parseRequiredTargetId(res: BrowserResponse, rawTargetId: unknown): string | null {
67
+ const targetId = toStringOrEmpty(rawTargetId);
68
+ if (!targetId) {
69
+ jsonError(res, 400, "targetId is required");
70
+ return null;
71
+ }
72
+ return targetId;
73
+ }
74
+
75
+ async function runTabTargetMutation(params: {
76
+ req: BrowserRequest;
77
+ res: BrowserResponse;
78
+ ctx: BrowserRouteContext;
79
+ targetId: string;
80
+ mutate: (profileCtx: ProfileContext, targetId: string) => Promise<void>;
81
+ }) {
82
+ await withTabsProfileRoute({
83
+ req: params.req,
84
+ res: params.res,
85
+ ctx: params.ctx,
86
+ mapTabError: true,
87
+ run: async (profileCtx) => {
88
+ if (!(await ensureBrowserRunning(profileCtx, params.res))) {
89
+ return;
90
+ }
91
+ await params.mutate(profileCtx, params.targetId);
92
+ params.res.json({ ok: true });
93
+ },
94
+ });
95
+ }
96
+
97
+ export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: BrowserRouteContext) {
98
+ app.get("/tabs", async (req, res) => {
99
+ await withTabsProfileRoute({
100
+ req,
101
+ res,
102
+ ctx,
103
+ run: async (profileCtx) => {
104
+ const reachable = await profileCtx.isReachable(300);
105
+ if (!reachable) {
106
+ return res.json({ running: false, tabs: [] as unknown[] });
107
+ }
108
+ const tabs = await profileCtx.listTabs();
109
+ res.json({ running: true, tabs });
110
+ },
111
+ });
112
+ });
113
+
114
+ app.post("/tabs/open", async (req, res) => {
115
+ const url = toStringOrEmpty((req.body as { url?: unknown })?.url);
116
+ if (!url) {
117
+ return jsonError(res, 400, "url is required");
118
+ }
119
+
120
+ await withTabsProfileRoute({
121
+ req,
122
+ res,
123
+ ctx,
124
+ mapTabError: true,
125
+ run: async (profileCtx) => {
126
+ await profileCtx.ensureBrowserAvailable();
127
+ const tab = await profileCtx.openTab(url);
128
+ res.json(tab);
129
+ },
130
+ });
131
+ });
132
+
133
+ app.post("/tabs/focus", async (req, res) => {
134
+ const targetId = parseRequiredTargetId(res, (req.body as { targetId?: unknown })?.targetId);
135
+ if (!targetId) {
136
+ return;
137
+ }
138
+ await runTabTargetMutation({
139
+ req,
140
+ res,
141
+ ctx,
142
+ targetId,
143
+ mutate: async (profileCtx, id) => {
144
+ await profileCtx.focusTab(id);
145
+ },
146
+ });
147
+ });
148
+
149
+ app.delete("/tabs/:targetId", async (req, res) => {
150
+ const targetId = parseRequiredTargetId(res, req.params.targetId);
151
+ if (!targetId) {
152
+ return;
153
+ }
154
+ await runTabTargetMutation({
155
+ req,
156
+ res,
157
+ ctx,
158
+ targetId,
159
+ mutate: async (profileCtx, id) => {
160
+ await profileCtx.closeTab(id);
161
+ },
162
+ });
163
+ });
164
+
165
+ app.post("/tabs/action", async (req, res) => {
166
+ const action = toStringOrEmpty((req.body as { action?: unknown })?.action);
167
+ const index = toNumber((req.body as { index?: unknown })?.index);
168
+
169
+ await withTabsProfileRoute({
170
+ req,
171
+ res,
172
+ ctx,
173
+ mapTabError: true,
174
+ run: async (profileCtx) => {
175
+ if (action === "list") {
176
+ const reachable = await profileCtx.isReachable(300);
177
+ if (!reachable) {
178
+ return res.json({ ok: true, tabs: [] as unknown[] });
179
+ }
180
+ const tabs = await profileCtx.listTabs();
181
+ return res.json({ ok: true, tabs });
182
+ }
183
+
184
+ if (action === "new") {
185
+ await profileCtx.ensureBrowserAvailable();
186
+ const tab = await profileCtx.openTab("about:blank");
187
+ return res.json({ ok: true, tab });
188
+ }
189
+
190
+ if (action === "close") {
191
+ const tabs = await profileCtx.listTabs();
192
+ const target = resolveIndexedTab(tabs, index);
193
+ if (!target) {
194
+ return jsonError(res, 404, "tab not found");
195
+ }
196
+ await profileCtx.closeTab(target.targetId);
197
+ return res.json({ ok: true, targetId: target.targetId });
198
+ }
199
+
200
+ if (action === "select") {
201
+ if (typeof index !== "number") {
202
+ return jsonError(res, 400, "index is required");
203
+ }
204
+ const tabs = await profileCtx.listTabs();
205
+ const target = tabs[index];
206
+ if (!target) {
207
+ return jsonError(res, 404, "tab not found");
208
+ }
209
+ await profileCtx.focusTab(target.targetId);
210
+ return res.json({ ok: true, targetId: target.targetId });
211
+ }
212
+
213
+ return jsonError(res, 400, "unknown tab action");
214
+ },
215
+ });
216
+ });
217
+ }
@@ -0,0 +1,26 @@
1
+ export type BrowserRequest = {
2
+ params: Record<string, string>;
3
+ query: Record<string, unknown>;
4
+ body?: unknown;
5
+ /**
6
+ * Optional abort signal for in-process dispatch. This lets callers enforce
7
+ * timeouts and (where supported) cancel long-running operations.
8
+ */
9
+ signal?: AbortSignal;
10
+ };
11
+
12
+ export type BrowserResponse = {
13
+ status: (code: number) => BrowserResponse;
14
+ json: (body: unknown) => void;
15
+ };
16
+
17
+ export type BrowserRouteHandler = (
18
+ req: BrowserRequest,
19
+ res: BrowserResponse,
20
+ ) => void | Promise<void>;
21
+
22
+ export type BrowserRouteRegistrar = {
23
+ get: (path: string, handler: BrowserRouteHandler) => void;
24
+ post: (path: string, handler: BrowserRouteHandler) => void;
25
+ delete: (path: string, handler: BrowserRouteHandler) => void;
26
+ };
@@ -0,0 +1,73 @@
1
+ import { parseBooleanValue } from "../enterprise-compat.js";
2
+ import type { BrowserRouteContext, ProfileContext } from "../server-context.js";
3
+ import type { BrowserRequest, BrowserResponse } from "./types.js";
4
+
5
+ /**
6
+ * Extract profile name from query string or body and get profile context.
7
+ * Query string takes precedence over body for consistency with GET routes.
8
+ */
9
+ export function getProfileContext(
10
+ req: BrowserRequest,
11
+ ctx: BrowserRouteContext,
12
+ ): ProfileContext | { error: string; status: number } {
13
+ let profileName: string | undefined;
14
+
15
+ // Check query string first (works for GET and POST)
16
+ if (typeof req.query.profile === "string") {
17
+ profileName = req.query.profile.trim() || undefined;
18
+ }
19
+
20
+ // Fall back to body for POST requests
21
+ if (!profileName && req.body && typeof req.body === "object") {
22
+ const body = req.body as Record<string, unknown>;
23
+ if (typeof body.profile === "string") {
24
+ profileName = body.profile.trim() || undefined;
25
+ }
26
+ }
27
+
28
+ try {
29
+ return ctx.forProfile(profileName);
30
+ } catch (err) {
31
+ return { error: String(err), status: 404 };
32
+ }
33
+ }
34
+
35
+ export function jsonError(res: BrowserResponse, status: number, message: string) {
36
+ res.status(status).json({ error: message });
37
+ }
38
+
39
+ export function toStringOrEmpty(value: unknown) {
40
+ if (typeof value === "string") {
41
+ return value.trim();
42
+ }
43
+ if (typeof value === "number" || typeof value === "boolean") {
44
+ return String(value).trim();
45
+ }
46
+ return "";
47
+ }
48
+
49
+ export function toNumber(value: unknown) {
50
+ if (typeof value === "number" && Number.isFinite(value)) {
51
+ return value;
52
+ }
53
+ if (typeof value === "string" && value.trim()) {
54
+ const parsed = Number(value);
55
+ return Number.isFinite(parsed) ? parsed : undefined;
56
+ }
57
+ return undefined;
58
+ }
59
+
60
+ export function toBoolean(value: unknown) {
61
+ return parseBooleanValue(value, {
62
+ truthy: ["true", "1", "yes"],
63
+ falsy: ["false", "0", "no"],
64
+ });
65
+ }
66
+
67
+ export function toStringArray(value: unknown): string[] | undefined {
68
+ if (!Array.isArray(value)) {
69
+ return undefined;
70
+ }
71
+ const strings = value.map((v) => toStringOrEmpty(v)).filter(Boolean);
72
+ return strings.length ? strings : undefined;
73
+ }
@@ -0,0 +1,54 @@
1
+ import { IMAGE_REDUCE_QUALITY_STEPS, buildImageResizeSideGrid, getImageMetadata, resizeToJpeg } from "./enterprise-compat.js";
2
+
3
+
4
+ export const DEFAULT_BROWSER_SCREENSHOT_MAX_SIDE = 2000;
5
+ export const DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
6
+
7
+ export async function normalizeBrowserScreenshot(
8
+ buffer: Buffer,
9
+ opts?: {
10
+ maxSide?: number;
11
+ maxBytes?: number;
12
+ },
13
+ ): Promise<{ buffer: Buffer; contentType?: "image/jpeg" }> {
14
+ const maxSide = Math.max(1, Math.round(opts?.maxSide ?? DEFAULT_BROWSER_SCREENSHOT_MAX_SIDE));
15
+ const maxBytes = Math.max(1, Math.round(opts?.maxBytes ?? DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES));
16
+
17
+ const meta = await getImageMetadata(buffer);
18
+ const width = Number(meta?.width ?? 0);
19
+ const height = Number(meta?.height ?? 0);
20
+ const maxDim = Math.max(width, height);
21
+
22
+ if (buffer.byteLength <= maxBytes && (maxDim === 0 || (width <= maxSide && height <= maxSide))) {
23
+ return { buffer };
24
+ }
25
+
26
+ const sideStart = maxDim > 0 ? Math.min(maxSide, maxDim) : maxSide;
27
+ const sideGrid = buildImageResizeSideGrid(maxSide, sideStart);
28
+
29
+ let smallest: { buffer: Buffer; size: number } | null = null;
30
+
31
+ for (const side of sideGrid) {
32
+ for (const quality of IMAGE_REDUCE_QUALITY_STEPS) {
33
+ const out = await resizeToJpeg({
34
+ buffer,
35
+ maxSide: side,
36
+ quality,
37
+ withoutEnlargement: true,
38
+ });
39
+
40
+ if (!smallest || out.byteLength < smallest.size) {
41
+ smallest = { buffer: out, size: out.byteLength };
42
+ }
43
+
44
+ if (out.byteLength <= maxBytes) {
45
+ return { buffer: out, contentType: "image/jpeg" };
46
+ }
47
+ }
48
+ }
49
+
50
+ const best = smallest?.buffer ?? buffer;
51
+ throw new Error(
52
+ `Browser screenshot could not be reduced below ${(maxBytes / (1024 * 1024)).toFixed(0)}MB (got ${(best.byteLength / (1024 * 1024)).toFixed(2)}MB)`,
53
+ );
54
+ }