@workglow/tasks 0.2.2 → 0.2.4
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/browser.js +421 -76
- package/dist/browser.js.map +9 -6
- package/dist/bun.d.ts +1 -0
- package/dist/bun.d.ts.map +1 -1
- package/dist/bun.js +540 -68
- package/dist/bun.js.map +11 -7
- package/dist/common.d.ts +3 -0
- package/dist/common.d.ts.map +1 -1
- package/dist/node.d.ts +1 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +540 -68
- package/dist/node.js.map +11 -7
- package/dist/task/FetchUrlTask.d.ts +13 -0
- package/dist/task/FetchUrlTask.d.ts.map +1 -1
- package/dist/task/image/imageCodecLimits.d.ts +80 -0
- package/dist/task/image/imageCodecLimits.d.ts.map +1 -0
- package/dist/task/image/imageRasterCodecBrowser.d.ts.map +1 -1
- package/dist/task/image/imageRasterCodecNode.d.ts.map +1 -1
- package/dist/util/SafeFetch.d.ts +51 -0
- package/dist/util/SafeFetch.d.ts.map +1 -0
- package/dist/util/SafeFetch.server.d.ts +22 -0
- package/dist/util/SafeFetch.server.d.ts.map +1 -0
- package/dist/util/UrlClassifier.d.ts +64 -0
- package/dist/util/UrlClassifier.d.ts.map +1 -0
- package/package.json +13 -9
package/dist/node.js
CHANGED
|
@@ -3,16 +3,70 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
3
3
|
|
|
4
4
|
// src/task/image/imageRasterCodecNode.ts
|
|
5
5
|
import { parseDataUri } from "@workglow/util/media";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
|
|
7
|
+
// src/task/image/imageCodecLimits.ts
|
|
8
|
+
var MAX_DECODED_PIXELS = 1e8;
|
|
9
|
+
var MAX_INPUT_BYTES_NODE = 64 * 1024 * 1024;
|
|
10
|
+
var MAX_INPUT_BYTES_BROWSER = 8 * 1024 * 1024;
|
|
11
|
+
var REJECTED_DECODE_MIME_TYPES = new Set([
|
|
12
|
+
"image/svg+xml",
|
|
13
|
+
"image/svg",
|
|
14
|
+
"image/gif",
|
|
15
|
+
"image/apng"
|
|
16
|
+
]);
|
|
17
|
+
var SUPPORTED_OUTPUT_MIME_TYPES = ["image/jpeg", "image/png", "image/webp"];
|
|
18
|
+
function assertWithinPixelBudget(width, height) {
|
|
19
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
|
|
20
|
+
throw new Error(`Image raster codec: invalid dimensions ${width}x${height}`);
|
|
21
|
+
}
|
|
22
|
+
const pixels = width * height;
|
|
23
|
+
if (pixels > MAX_DECODED_PIXELS) {
|
|
24
|
+
throw new Error(`Image raster codec: decoded image exceeds pixel budget ` + `(${width}x${height} = ${pixels} > ${MAX_DECODED_PIXELS})`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function assertWithinByteBudget(byteLength, limit) {
|
|
28
|
+
if (byteLength > limit) {
|
|
29
|
+
throw new Error(`Image raster codec: input exceeds byte budget (${byteLength} > ${limit})`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function formatDataUriErrorPreview(value) {
|
|
33
|
+
if (typeof value !== "string") {
|
|
34
|
+
return String(value);
|
|
35
|
+
}
|
|
36
|
+
if (value.startsWith("data:")) {
|
|
37
|
+
const commaIndex = value.indexOf(",");
|
|
38
|
+
if (commaIndex >= 0) {
|
|
39
|
+
return `${value.slice(0, commaIndex)},[REDACTED]`;
|
|
40
|
+
}
|
|
41
|
+
return `${value.slice(0, 80)}${value.length > 80 ? "…" : ""}`;
|
|
42
|
+
}
|
|
43
|
+
return `${value.slice(0, 80)}${value.length > 80 ? "…" : ""}`;
|
|
44
|
+
}
|
|
45
|
+
function assertIsDataUri(value) {
|
|
46
|
+
if (typeof value !== "string" || !value.startsWith("data:")) {
|
|
47
|
+
const preview = formatDataUriErrorPreview(value);
|
|
48
|
+
throw new Error(`Image raster codec: expected a data: URI but received "${preview}"`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function extractDataUriMimeType(dataUri) {
|
|
52
|
+
const match = dataUri.match(/^data:([^;,]+)/);
|
|
53
|
+
return match?.[1]?.trim().toLowerCase();
|
|
54
|
+
}
|
|
55
|
+
function normalizeOutputMimeType(mimeType) {
|
|
56
|
+
const m = mimeType.toLowerCase().trim();
|
|
57
|
+
if (m === "image/jpeg" || m === "image/jpg") {
|
|
9
58
|
return "image/jpeg";
|
|
10
59
|
}
|
|
11
|
-
if (m
|
|
60
|
+
if (m === "image/png") {
|
|
61
|
+
return "image/png";
|
|
62
|
+
}
|
|
63
|
+
if (m === "image/webp") {
|
|
12
64
|
return "image/webp";
|
|
13
65
|
}
|
|
14
|
-
|
|
66
|
+
throw new Error(`Image raster codec: unsupported output mime type "${mimeType}". ` + `Supported: ${SUPPORTED_OUTPUT_MIME_TYPES.join(", ")}.`);
|
|
15
67
|
}
|
|
68
|
+
|
|
69
|
+
// src/task/image/imageRasterCodecNode.ts
|
|
16
70
|
function expandGrayAlphaToRgba(src, width, height) {
|
|
17
71
|
const n = width * height;
|
|
18
72
|
const dst = new Uint8ClampedArray(n * 4);
|
|
@@ -38,13 +92,24 @@ async function getSharp() {
|
|
|
38
92
|
return _sharp;
|
|
39
93
|
}
|
|
40
94
|
async function decodeDataUri(dataUri) {
|
|
95
|
+
assertIsDataUri(dataUri);
|
|
96
|
+
const declaredMime = extractDataUriMimeType(dataUri);
|
|
97
|
+
if (declaredMime && REJECTED_DECODE_MIME_TYPES.has(declaredMime)) {
|
|
98
|
+
throw new Error(`Image raster codec: refusing to rasterize "${declaredMime}". Vector and animated formats lose information when converted to pixels. Convert to PNG, JPEG, or WebP before passing to the codec.`);
|
|
99
|
+
}
|
|
41
100
|
const sharp = await getSharp();
|
|
42
101
|
const { base64 } = parseDataUri(dataUri);
|
|
102
|
+
const estimatedBytes = Math.ceil(base64.length * 3 / 4);
|
|
103
|
+
assertWithinByteBudget(estimatedBytes, MAX_INPUT_BYTES_NODE);
|
|
43
104
|
const buffer = Buffer.from(base64, "base64");
|
|
44
|
-
const { data, info } = await sharp(buffer
|
|
105
|
+
const { data, info } = await sharp(buffer, {
|
|
106
|
+
limitInputPixels: MAX_DECODED_PIXELS,
|
|
107
|
+
sequentialRead: true
|
|
108
|
+
}).raw().toBuffer({ resolveWithObject: true });
|
|
45
109
|
const width = info.width;
|
|
46
110
|
const height = info.height;
|
|
47
111
|
const ch = info.channels;
|
|
112
|
+
assertWithinPixelBudget(width, height);
|
|
48
113
|
if (ch === 2) {
|
|
49
114
|
return {
|
|
50
115
|
data: expandGrayAlphaToRgba(data, width, height),
|
|
@@ -55,7 +120,7 @@ async function decodeDataUri(dataUri) {
|
|
|
55
120
|
}
|
|
56
121
|
if (ch === 1 || ch === 3 || ch === 4) {
|
|
57
122
|
return {
|
|
58
|
-
data: new Uint8ClampedArray(data
|
|
123
|
+
data: new Uint8ClampedArray(data),
|
|
59
124
|
width,
|
|
60
125
|
height,
|
|
61
126
|
channels: ch
|
|
@@ -66,7 +131,7 @@ async function decodeDataUri(dataUri) {
|
|
|
66
131
|
async function encodeDataUri(image, mimeType) {
|
|
67
132
|
const sharp = await getSharp();
|
|
68
133
|
const { data, width, height, channels } = image;
|
|
69
|
-
const fmt =
|
|
134
|
+
const fmt = normalizeOutputMimeType(mimeType);
|
|
70
135
|
const base = sharp(Buffer.from(data), { raw: { width, height, channels } });
|
|
71
136
|
const out = fmt === "image/jpeg" ? await base.jpeg({ quality: 92, mozjpeg: true }).toBuffer() : fmt === "image/webp" ? await base.webp({ quality: 92 }).toBuffer() : await base.png({ compressionLevel: 6 }).toBuffer();
|
|
72
137
|
return `data:${fmt};base64,${out.toString("base64")}`;
|
|
@@ -90,6 +155,405 @@ function getImageRasterCodec() {
|
|
|
90
155
|
// src/task/image/registerImageRasterCodec.node.ts
|
|
91
156
|
registerImageRasterCodec(createNodeImageRasterCodec());
|
|
92
157
|
|
|
158
|
+
// src/util/SafeFetch.server.ts
|
|
159
|
+
import { PermanentJobError as PermanentJobError2 } from "@workglow/job-queue";
|
|
160
|
+
import { lookup as dnsLookup } from "node:dns/promises";
|
|
161
|
+
import { Agent, fetch as undiciFetch } from "undici";
|
|
162
|
+
|
|
163
|
+
// src/util/UrlClassifier.ts
|
|
164
|
+
import ipaddr from "ipaddr.js";
|
|
165
|
+
var PRIVATE_EXACT_HOSTS = new Set([
|
|
166
|
+
"localhost",
|
|
167
|
+
"localhost.localdomain",
|
|
168
|
+
"ip6-localhost",
|
|
169
|
+
"ip6-loopback",
|
|
170
|
+
"metadata.google.internal",
|
|
171
|
+
"metadata.internal",
|
|
172
|
+
"metadata.azure.com",
|
|
173
|
+
"instance-data"
|
|
174
|
+
]);
|
|
175
|
+
var PRIVATE_DOMAIN_SUFFIXES = [
|
|
176
|
+
"local",
|
|
177
|
+
"localhost",
|
|
178
|
+
"internal",
|
|
179
|
+
"lan",
|
|
180
|
+
"home.arpa",
|
|
181
|
+
"corp",
|
|
182
|
+
"intranet",
|
|
183
|
+
"private",
|
|
184
|
+
"localdomain"
|
|
185
|
+
];
|
|
186
|
+
var PRIVATE_IPV4_RANGES = new Set([
|
|
187
|
+
"unspecified",
|
|
188
|
+
"broadcast",
|
|
189
|
+
"multicast",
|
|
190
|
+
"linkLocal",
|
|
191
|
+
"loopback",
|
|
192
|
+
"carrierGradeNat",
|
|
193
|
+
"private",
|
|
194
|
+
"reserved",
|
|
195
|
+
"benchmarking"
|
|
196
|
+
]);
|
|
197
|
+
var PRIVATE_IPV6_RANGES = new Set([
|
|
198
|
+
"unspecified",
|
|
199
|
+
"linkLocal",
|
|
200
|
+
"multicast",
|
|
201
|
+
"loopback",
|
|
202
|
+
"uniqueLocal",
|
|
203
|
+
"ipv4Mapped",
|
|
204
|
+
"ipv4Compat",
|
|
205
|
+
"rfc6145",
|
|
206
|
+
"rfc6052",
|
|
207
|
+
"6to4",
|
|
208
|
+
"teredo",
|
|
209
|
+
"reserved",
|
|
210
|
+
"benchmarking",
|
|
211
|
+
"amt",
|
|
212
|
+
"as112v6",
|
|
213
|
+
"deprecated",
|
|
214
|
+
"orchid2",
|
|
215
|
+
"droneRemoteIdProtocolEntityTags"
|
|
216
|
+
]);
|
|
217
|
+
function tryNormalizeIPv4(host) {
|
|
218
|
+
if (host.length === 0)
|
|
219
|
+
return;
|
|
220
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) {
|
|
221
|
+
const octets = host.split(".").map((s) => parseInt(s, 10));
|
|
222
|
+
if (octets.every((n) => n >= 0 && n <= 255)) {
|
|
223
|
+
return octets.join(".");
|
|
224
|
+
}
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const parts = host.split(".");
|
|
228
|
+
if (parts.length < 1 || parts.length > 4)
|
|
229
|
+
return;
|
|
230
|
+
const nums = [];
|
|
231
|
+
for (const p of parts) {
|
|
232
|
+
if (p.length === 0)
|
|
233
|
+
return;
|
|
234
|
+
let n;
|
|
235
|
+
if (/^0[xX][0-9a-fA-F]+$/.test(p)) {
|
|
236
|
+
n = parseInt(p.slice(2), 16);
|
|
237
|
+
} else if (/^0[0-7]+$/.test(p)) {
|
|
238
|
+
n = parseInt(p, 8);
|
|
239
|
+
} else if (/^\d+$/.test(p)) {
|
|
240
|
+
n = parseInt(p, 10);
|
|
241
|
+
} else {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (!Number.isFinite(n) || n < 0)
|
|
245
|
+
return;
|
|
246
|
+
nums.push(n);
|
|
247
|
+
}
|
|
248
|
+
let addr;
|
|
249
|
+
if (nums.length === 1) {
|
|
250
|
+
if (nums[0] > 4294967295)
|
|
251
|
+
return;
|
|
252
|
+
addr = nums[0];
|
|
253
|
+
} else if (nums.length === 2) {
|
|
254
|
+
if (nums[0] > 255 || nums[1] > 16777215)
|
|
255
|
+
return;
|
|
256
|
+
addr = nums[0] * 16777216 + nums[1];
|
|
257
|
+
} else if (nums.length === 3) {
|
|
258
|
+
if (nums[0] > 255 || nums[1] > 255 || nums[2] > 65535)
|
|
259
|
+
return;
|
|
260
|
+
addr = nums[0] * 16777216 + nums[1] * 65536 + nums[2];
|
|
261
|
+
} else {
|
|
262
|
+
if (nums.some((n) => n > 255))
|
|
263
|
+
return;
|
|
264
|
+
addr = nums[0] * 16777216 + nums[1] * 65536 + nums[2] * 256 + nums[3];
|
|
265
|
+
}
|
|
266
|
+
const o1 = Math.floor(addr / 16777216) & 255;
|
|
267
|
+
const o2 = Math.floor(addr / 65536) & 255;
|
|
268
|
+
const o3 = Math.floor(addr / 256) & 255;
|
|
269
|
+
const o4 = addr & 255;
|
|
270
|
+
return `${o1}.${o2}.${o3}.${o4}`;
|
|
271
|
+
}
|
|
272
|
+
function classifyIpLiteral(host) {
|
|
273
|
+
if (host.includes(":")) {
|
|
274
|
+
let ipv6;
|
|
275
|
+
try {
|
|
276
|
+
ipv6 = ipaddr.IPv6.parse(host);
|
|
277
|
+
} catch {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (ipv6.isIPv4MappedAddress()) {
|
|
281
|
+
const v4 = ipv6.toIPv4Address();
|
|
282
|
+
const range3 = v4.range();
|
|
283
|
+
const canonical3 = v4.toNormalizedString();
|
|
284
|
+
if (PRIVATE_IPV4_RANGES.has(range3)) {
|
|
285
|
+
return { kind: "private", reason: `IPv4-mapped IPv6 in ${range3} range`, canonical: canonical3 };
|
|
286
|
+
}
|
|
287
|
+
return { kind: "public", canonical: canonical3 };
|
|
288
|
+
}
|
|
289
|
+
const range2 = ipv6.range();
|
|
290
|
+
const canonical2 = ipv6.toNormalizedString();
|
|
291
|
+
if (PRIVATE_IPV6_RANGES.has(range2)) {
|
|
292
|
+
return { kind: "private", reason: `IPv6 in ${range2} range`, canonical: canonical2 };
|
|
293
|
+
}
|
|
294
|
+
return { kind: "public", canonical: canonical2 };
|
|
295
|
+
}
|
|
296
|
+
const canonical = tryNormalizeIPv4(host);
|
|
297
|
+
if (canonical === undefined)
|
|
298
|
+
return;
|
|
299
|
+
let ipv4;
|
|
300
|
+
try {
|
|
301
|
+
ipv4 = ipaddr.IPv4.parse(canonical);
|
|
302
|
+
} catch {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const range = ipv4.range();
|
|
306
|
+
if (PRIVATE_IPV4_RANGES.has(range)) {
|
|
307
|
+
return { kind: "private", reason: `IPv4 in ${range} range`, canonical };
|
|
308
|
+
}
|
|
309
|
+
return { kind: "public", canonical };
|
|
310
|
+
}
|
|
311
|
+
function normalizeHost(host) {
|
|
312
|
+
let h = host;
|
|
313
|
+
if (h.startsWith("[") && h.endsWith("]")) {
|
|
314
|
+
h = h.slice(1, -1);
|
|
315
|
+
}
|
|
316
|
+
while (h.endsWith(".")) {
|
|
317
|
+
h = h.slice(0, -1);
|
|
318
|
+
}
|
|
319
|
+
return h.toLowerCase();
|
|
320
|
+
}
|
|
321
|
+
function matchesPrivateHostnamePattern(host) {
|
|
322
|
+
if (PRIVATE_EXACT_HOSTS.has(host)) {
|
|
323
|
+
return `host '${host}' is a reserved private name`;
|
|
324
|
+
}
|
|
325
|
+
for (const suffix of PRIVATE_DOMAIN_SUFFIXES) {
|
|
326
|
+
if (host === suffix || host.endsWith("." + suffix)) {
|
|
327
|
+
return `host matches private suffix '.${suffix}'`;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
function classifyUrl(urlStr) {
|
|
333
|
+
if (typeof urlStr !== "string" || urlStr.length === 0) {
|
|
334
|
+
return { kind: "invalid", reason: "empty or non-string URL" };
|
|
335
|
+
}
|
|
336
|
+
let parsed;
|
|
337
|
+
try {
|
|
338
|
+
parsed = new URL(urlStr);
|
|
339
|
+
} catch {
|
|
340
|
+
return { kind: "invalid", reason: "malformed URL" };
|
|
341
|
+
}
|
|
342
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
343
|
+
return { kind: "invalid", reason: `unsupported protocol '${parsed.protocol}'` };
|
|
344
|
+
}
|
|
345
|
+
if (parsed.username.length > 0 || parsed.password.length > 0) {
|
|
346
|
+
return { kind: "invalid", reason: "URL credentials are not allowed" };
|
|
347
|
+
}
|
|
348
|
+
const host = normalizeHost(parsed.hostname);
|
|
349
|
+
if (host.length === 0) {
|
|
350
|
+
return { kind: "invalid", reason: "empty host" };
|
|
351
|
+
}
|
|
352
|
+
const ipClassification = classifyIpLiteral(host);
|
|
353
|
+
if (ipClassification !== undefined) {
|
|
354
|
+
return {
|
|
355
|
+
kind: ipClassification.kind,
|
|
356
|
+
reason: ipClassification.reason,
|
|
357
|
+
host,
|
|
358
|
+
literalIp: ipClassification.canonical
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
const hostnameReason = matchesPrivateHostnamePattern(host);
|
|
362
|
+
if (hostnameReason !== undefined) {
|
|
363
|
+
return { kind: "private", reason: hostnameReason, host };
|
|
364
|
+
}
|
|
365
|
+
return { kind: "public", host };
|
|
366
|
+
}
|
|
367
|
+
function urlResourcePattern(urlStr) {
|
|
368
|
+
let parsed;
|
|
369
|
+
try {
|
|
370
|
+
parsed = new URL(urlStr);
|
|
371
|
+
} catch {
|
|
372
|
+
return urlStr;
|
|
373
|
+
}
|
|
374
|
+
const origin = parsed.port.length > 0 ? `${parsed.protocol}//${parsed.host}` : `${parsed.protocol}//${parsed.hostname}`;
|
|
375
|
+
return `${origin}/*`;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// src/util/SafeFetch.ts
|
|
379
|
+
import { PermanentJobError } from "@workglow/job-queue";
|
|
380
|
+
var MAX_REDIRECT_HOPS = 20;
|
|
381
|
+
function assertAllowedUrl(url, allowPrivate) {
|
|
382
|
+
const classification = classifyUrl(url);
|
|
383
|
+
if (classification.kind === "invalid") {
|
|
384
|
+
throw new PermanentJobError(`Refusing to fetch invalid URL: ${classification.reason}`);
|
|
385
|
+
}
|
|
386
|
+
if (classification.kind === "private" && !allowPrivate) {
|
|
387
|
+
throw new PermanentJobError(`Refusing to fetch private/internal URL ${url}: ${classification.reason}. ` + `Grant the 'network:private' entitlement to allow this request.`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function isRedirectStatus(status) {
|
|
391
|
+
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
392
|
+
}
|
|
393
|
+
async function defaultSafeFetch(url, options) {
|
|
394
|
+
const requestedRedirectMode = options.redirect ?? "follow";
|
|
395
|
+
const { allowPrivate, redirect: _redirect, ...fetchOptions } = options;
|
|
396
|
+
let currentUrl = url;
|
|
397
|
+
for (let hops = 0;hops <= MAX_REDIRECT_HOPS; hops += 1) {
|
|
398
|
+
assertAllowedUrl(currentUrl, allowPrivate);
|
|
399
|
+
const response = await globalThis.fetch(currentUrl, {
|
|
400
|
+
...fetchOptions,
|
|
401
|
+
redirect: "manual"
|
|
402
|
+
});
|
|
403
|
+
if (!isRedirectStatus(response.status)) {
|
|
404
|
+
return response;
|
|
405
|
+
}
|
|
406
|
+
if (requestedRedirectMode === "manual") {
|
|
407
|
+
return response;
|
|
408
|
+
}
|
|
409
|
+
if (requestedRedirectMode === "error") {
|
|
410
|
+
throw new TypeError(`Fetch for ${currentUrl} failed because redirect mode was set to 'error'.`);
|
|
411
|
+
}
|
|
412
|
+
const location = response.headers.get("location");
|
|
413
|
+
if (!location) {
|
|
414
|
+
throw new PermanentJobError(`Refusing to follow redirect from ${currentUrl}: missing Location header.`);
|
|
415
|
+
}
|
|
416
|
+
currentUrl = new URL(location, currentUrl).toString();
|
|
417
|
+
}
|
|
418
|
+
throw new PermanentJobError(`Refusing to fetch ${url}: too many redirects.`);
|
|
419
|
+
}
|
|
420
|
+
var currentImpl = defaultSafeFetch;
|
|
421
|
+
function registerSafeFetch(fn) {
|
|
422
|
+
const previousImpl = currentImpl;
|
|
423
|
+
currentImpl = fn;
|
|
424
|
+
return previousImpl;
|
|
425
|
+
}
|
|
426
|
+
function getSafeFetchImpl() {
|
|
427
|
+
return currentImpl;
|
|
428
|
+
}
|
|
429
|
+
function resetSafeFetch() {
|
|
430
|
+
currentImpl = defaultSafeFetch;
|
|
431
|
+
}
|
|
432
|
+
function safeFetch(url, options = {}) {
|
|
433
|
+
return currentImpl(url, options);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/util/SafeFetch.server.ts
|
|
437
|
+
var MAX_REDIRECT_HOPS2 = 20;
|
|
438
|
+
async function resolveAll(hostname) {
|
|
439
|
+
try {
|
|
440
|
+
const addrs = await dnsLookup(hostname, { all: true, verbatim: true });
|
|
441
|
+
if (!Array.isArray(addrs) || addrs.length === 0) {
|
|
442
|
+
throw new PermanentJobError2(`DNS lookup returned no addresses for '${hostname}'`);
|
|
443
|
+
}
|
|
444
|
+
return addrs.map((a) => ({ address: a.address, family: a.family }));
|
|
445
|
+
} catch (err) {
|
|
446
|
+
if (err instanceof PermanentJobError2)
|
|
447
|
+
throw err;
|
|
448
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
449
|
+
throw new PermanentJobError2(`DNS lookup failed for '${hostname}': ${msg}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function isLiteralHost(host) {
|
|
453
|
+
if (host.includes(":"))
|
|
454
|
+
return true;
|
|
455
|
+
return /^[0-9a-fA-FxX.]+$/.test(host);
|
|
456
|
+
}
|
|
457
|
+
function isRedirectStatus2(status) {
|
|
458
|
+
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
459
|
+
}
|
|
460
|
+
async function fetchOneHop(url, opts, fetchInit) {
|
|
461
|
+
const classification = classifyUrl(url);
|
|
462
|
+
if (classification.kind === "invalid") {
|
|
463
|
+
throw new PermanentJobError2(`Refusing to fetch invalid URL: ${classification.reason}`);
|
|
464
|
+
}
|
|
465
|
+
if (classification.kind === "private" && !opts.allowPrivate) {
|
|
466
|
+
throw new PermanentJobError2(`Refusing to fetch private/internal URL ${url}: ${classification.reason}. ` + `Grant the 'network:private' entitlement to allow this request.`);
|
|
467
|
+
}
|
|
468
|
+
const parsed = new URL(url);
|
|
469
|
+
const host = classification.host ?? parsed.hostname.toLowerCase();
|
|
470
|
+
let pinned;
|
|
471
|
+
if (isLiteralHost(host) && classification.literalIp !== undefined) {
|
|
472
|
+
pinned = {
|
|
473
|
+
address: classification.literalIp,
|
|
474
|
+
family: classification.literalIp.includes(":") ? 6 : 4
|
|
475
|
+
};
|
|
476
|
+
} else {
|
|
477
|
+
const addrs = await resolveAll(host);
|
|
478
|
+
for (const addr of addrs) {
|
|
479
|
+
const ipClass = classifyIpLiteral(addr.address);
|
|
480
|
+
if (ipClass === undefined) {
|
|
481
|
+
throw new PermanentJobError2(`DNS resolved '${host}' to an unparseable address '${addr.address}'`);
|
|
482
|
+
}
|
|
483
|
+
if (ipClass.kind === "private" && !opts.allowPrivate) {
|
|
484
|
+
throw new PermanentJobError2(`Refusing to fetch ${url}: hostname '${host}' resolved to private address ` + `${addr.address} (${ipClass.reason}). This may indicate DNS rebinding. ` + `Grant the 'network:private' entitlement to allow this request.`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
pinned = addrs[0];
|
|
488
|
+
}
|
|
489
|
+
const pinnedAddress = pinned.address;
|
|
490
|
+
const pinnedFamily = pinned.family;
|
|
491
|
+
const dispatcher = new Agent({
|
|
492
|
+
connect: {
|
|
493
|
+
lookup: (_hostname, _lookupOptions, cb) => {
|
|
494
|
+
cb(null, pinnedAddress, pinnedFamily);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
try {
|
|
499
|
+
const response = await undiciFetch(url, {
|
|
500
|
+
...fetchInit,
|
|
501
|
+
dispatcher,
|
|
502
|
+
redirect: "manual"
|
|
503
|
+
});
|
|
504
|
+
return { response, dispatcher };
|
|
505
|
+
} catch (err) {
|
|
506
|
+
await dispatcher.close().catch(() => {});
|
|
507
|
+
throw err;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
var serverSafeFetch = async (url, options) => {
|
|
511
|
+
const opts = options ?? {};
|
|
512
|
+
const requestedRedirectMode = opts.redirect ?? "follow";
|
|
513
|
+
const { allowPrivate: _allowPrivate, redirect: _redirect, ...fetchInit } = opts;
|
|
514
|
+
let currentUrl = url;
|
|
515
|
+
let prevDispatcher;
|
|
516
|
+
for (let hops = 0;hops <= MAX_REDIRECT_HOPS2; hops += 1) {
|
|
517
|
+
const { response, dispatcher } = await fetchOneHop(currentUrl, opts, fetchInit);
|
|
518
|
+
if (prevDispatcher !== undefined) {
|
|
519
|
+
prevDispatcher.close().catch(() => {});
|
|
520
|
+
}
|
|
521
|
+
if (!isRedirectStatus2(response.status)) {
|
|
522
|
+
const body = response.body;
|
|
523
|
+
if (body !== null) {
|
|
524
|
+
const { readable, writable } = new TransformStream;
|
|
525
|
+
body.pipeTo(writable).finally(() => {
|
|
526
|
+
dispatcher.close().catch(() => {});
|
|
527
|
+
});
|
|
528
|
+
return new Response(readable, {
|
|
529
|
+
status: response.status,
|
|
530
|
+
statusText: response.statusText,
|
|
531
|
+
headers: response.headers
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
dispatcher.close().catch(() => {});
|
|
535
|
+
return response;
|
|
536
|
+
}
|
|
537
|
+
if (requestedRedirectMode === "manual") {
|
|
538
|
+
dispatcher.close().catch(() => {});
|
|
539
|
+
return response;
|
|
540
|
+
}
|
|
541
|
+
if (requestedRedirectMode === "error") {
|
|
542
|
+
dispatcher.close().catch(() => {});
|
|
543
|
+
throw new TypeError(`Fetch for ${currentUrl} failed because redirect mode was set to 'error'.`);
|
|
544
|
+
}
|
|
545
|
+
const location = response.headers.get("location");
|
|
546
|
+
if (!location) {
|
|
547
|
+
dispatcher.close().catch(() => {});
|
|
548
|
+
throw new PermanentJobError2(`Refusing to follow redirect from ${currentUrl}: missing Location header.`);
|
|
549
|
+
}
|
|
550
|
+
prevDispatcher = dispatcher;
|
|
551
|
+
currentUrl = new URL(location, currentUrl).toString();
|
|
552
|
+
}
|
|
553
|
+
throw new PermanentJobError2(`Refusing to fetch ${url}: too many redirects.`);
|
|
554
|
+
};
|
|
555
|
+
registerSafeFetch(serverSafeFetch);
|
|
556
|
+
|
|
93
557
|
// src/task/adaptive.ts
|
|
94
558
|
import { CreateAdaptiveWorkflow, Workflow as Workflow10 } from "@workglow/task-graph";
|
|
95
559
|
|
|
@@ -625,7 +1089,6 @@ Workflow10.prototype.subtract = CreateAdaptiveWorkflow(ScalarSubtractTask, Vecto
|
|
|
625
1089
|
Workflow10.prototype.multiply = CreateAdaptiveWorkflow(ScalarMultiplyTask, VectorMultiplyTask);
|
|
626
1090
|
Workflow10.prototype.divide = CreateAdaptiveWorkflow(ScalarDivideTask, VectorDivideTask);
|
|
627
1091
|
Workflow10.prototype.sum = CreateAdaptiveWorkflow(ScalarSumTask, VectorSumTask);
|
|
628
|
-
|
|
629
1092
|
// src/mcp-server/getMcpServerConfig.ts
|
|
630
1093
|
function getMcpServerConfig(configOrInput) {
|
|
631
1094
|
const server = configOrInput.server;
|
|
@@ -1813,7 +2276,7 @@ Workflow13.prototype.delay = CreateWorkflow12(DelayTask);
|
|
|
1813
2276
|
import {
|
|
1814
2277
|
AbortSignalJobError,
|
|
1815
2278
|
Job,
|
|
1816
|
-
PermanentJobError,
|
|
2279
|
+
PermanentJobError as PermanentJobError3,
|
|
1817
2280
|
RetryableJobError
|
|
1818
2281
|
} from "@workglow/job-queue";
|
|
1819
2282
|
import {
|
|
@@ -1822,52 +2285,13 @@ import {
|
|
|
1822
2285
|
getJobQueueFactory,
|
|
1823
2286
|
getTaskQueueRegistry,
|
|
1824
2287
|
JobTaskFailedError,
|
|
2288
|
+
mergeEntitlements,
|
|
1825
2289
|
Task as Task13,
|
|
1826
2290
|
TaskConfigSchema as TaskConfigSchema3,
|
|
1827
2291
|
TaskConfigurationError,
|
|
1828
2292
|
TaskInvalidInputError as TaskInvalidInputError2,
|
|
1829
2293
|
Workflow as Workflow14
|
|
1830
2294
|
} from "@workglow/task-graph";
|
|
1831
|
-
var PRIVATE_IP_RANGES = [
|
|
1832
|
-
/^127\./,
|
|
1833
|
-
/^10\./,
|
|
1834
|
-
/^172\.(1[6-9]|2\d|3[01])\./,
|
|
1835
|
-
/^192\.168\./,
|
|
1836
|
-
/^169\.254\./,
|
|
1837
|
-
/^0\./,
|
|
1838
|
-
/^fc00:/i,
|
|
1839
|
-
/^fe80:/i,
|
|
1840
|
-
/^::1$/,
|
|
1841
|
-
/^::$/
|
|
1842
|
-
];
|
|
1843
|
-
var PRIVATE_HOSTNAMES = new Set(["localhost", "metadata.google.internal", "metadata.internal"]);
|
|
1844
|
-
function isAllowPrivateUrlsEnvSet() {
|
|
1845
|
-
if (globalThis?.process?.env?.WORKGLOW_ALLOW_PRIVATE_URLS === "true") {
|
|
1846
|
-
return true;
|
|
1847
|
-
}
|
|
1848
|
-
const viteEnv = import.meta.env;
|
|
1849
|
-
return viteEnv?.VITE_WORKGLOW_ALLOW_PRIVATE_URLS === "true";
|
|
1850
|
-
}
|
|
1851
|
-
function isPrivateUrl(urlStr) {
|
|
1852
|
-
if (isAllowPrivateUrlsEnvSet()) {
|
|
1853
|
-
return false;
|
|
1854
|
-
}
|
|
1855
|
-
try {
|
|
1856
|
-
const parsed = new URL(urlStr);
|
|
1857
|
-
const hostname = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
1858
|
-
if (PRIVATE_HOSTNAMES.has(hostname) || hostname.endsWith(".local")) {
|
|
1859
|
-
return true;
|
|
1860
|
-
}
|
|
1861
|
-
for (const range of PRIVATE_IP_RANGES) {
|
|
1862
|
-
if (range.test(hostname)) {
|
|
1863
|
-
return true;
|
|
1864
|
-
}
|
|
1865
|
-
}
|
|
1866
|
-
return false;
|
|
1867
|
-
} catch {
|
|
1868
|
-
return false;
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
2295
|
var inputSchema13 = {
|
|
1872
2296
|
type: "object",
|
|
1873
2297
|
properties: {
|
|
@@ -1955,7 +2379,7 @@ async function fetchWithProgress(url, options = {}, onProgress) {
|
|
|
1955
2379
|
if (!options.signal) {
|
|
1956
2380
|
throw new TaskConfigurationError("An AbortSignal must be provided.");
|
|
1957
2381
|
}
|
|
1958
|
-
const response = await
|
|
2382
|
+
const response = await safeFetch(url, options);
|
|
1959
2383
|
if (!response.body) {
|
|
1960
2384
|
throw new Error("ReadableStream not supported in this environment.");
|
|
1961
2385
|
}
|
|
@@ -2004,14 +2428,16 @@ async function fetchWithProgress(url, options = {}, onProgress) {
|
|
|
2004
2428
|
class FetchUrlJob extends Job {
|
|
2005
2429
|
static type = "FetchUrlJob";
|
|
2006
2430
|
async execute(input, context) {
|
|
2007
|
-
|
|
2008
|
-
|
|
2431
|
+
const classification = classifyUrl(input.url);
|
|
2432
|
+
if (classification.kind === "invalid") {
|
|
2433
|
+
throw new PermanentJobError3(`Refusing to fetch invalid URL ${input.url}: ${classification.reason ?? "malformed"}`);
|
|
2009
2434
|
}
|
|
2010
2435
|
const response = await fetchWithProgress(input.url, {
|
|
2011
2436
|
method: input.method,
|
|
2012
2437
|
headers: input.headers,
|
|
2013
2438
|
body: input.body,
|
|
2014
|
-
signal: context.signal
|
|
2439
|
+
signal: context.signal,
|
|
2440
|
+
allowPrivate: classification.kind === "private"
|
|
2015
2441
|
}, async (progress) => await context.updateProgress(progress));
|
|
2016
2442
|
if (response.ok) {
|
|
2017
2443
|
const contentType = response.headers.get("content-type") ?? "";
|
|
@@ -2064,7 +2490,7 @@ class FetchUrlJob extends Job {
|
|
|
2064
2490
|
}
|
|
2065
2491
|
throw new RetryableJobError(`Failed to fetch ${input.url}: ${response.status} ${response.statusText}`, retryDate);
|
|
2066
2492
|
} else {
|
|
2067
|
-
throw new
|
|
2493
|
+
throw new PermanentJobError3(`Failed to fetch ${input.url}: ${response.status} ${response.statusText}`);
|
|
2068
2494
|
}
|
|
2069
2495
|
}
|
|
2070
2496
|
}
|
|
@@ -2088,6 +2514,7 @@ class FetchUrlTask extends Task13 {
|
|
|
2088
2514
|
static title = "Fetch";
|
|
2089
2515
|
static description = "Fetches data from a URL with progress tracking and automatic retry handling";
|
|
2090
2516
|
static hasDynamicSchemas = true;
|
|
2517
|
+
static hasDynamicEntitlements = true;
|
|
2091
2518
|
static entitlements() {
|
|
2092
2519
|
return {
|
|
2093
2520
|
entitlements: [
|
|
@@ -2100,6 +2527,33 @@ class FetchUrlTask extends Task13 {
|
|
|
2100
2527
|
]
|
|
2101
2528
|
};
|
|
2102
2529
|
}
|
|
2530
|
+
entitlements() {
|
|
2531
|
+
const base = FetchUrlTask.entitlements();
|
|
2532
|
+
const url = this.runInputData?.url;
|
|
2533
|
+
if (typeof url !== "string" || url.length === 0) {
|
|
2534
|
+
return mergeEntitlements(base, {
|
|
2535
|
+
entitlements: [
|
|
2536
|
+
{
|
|
2537
|
+
id: Entitlements.NETWORK_PRIVATE,
|
|
2538
|
+
reason: "Runtime URL is not yet available during entitlement evaluation; private/internal destinations must be explicitly allowed"
|
|
2539
|
+
}
|
|
2540
|
+
]
|
|
2541
|
+
});
|
|
2542
|
+
}
|
|
2543
|
+
const classification = classifyUrl(url);
|
|
2544
|
+
if (classification.kind !== "private") {
|
|
2545
|
+
return base;
|
|
2546
|
+
}
|
|
2547
|
+
return mergeEntitlements(base, {
|
|
2548
|
+
entitlements: [
|
|
2549
|
+
{
|
|
2550
|
+
id: Entitlements.NETWORK_PRIVATE,
|
|
2551
|
+
reason: `URL targets private/internal host: ${classification.reason ?? classification.host ?? "unknown"}`,
|
|
2552
|
+
resources: [urlResourcePattern(url)]
|
|
2553
|
+
}
|
|
2554
|
+
]
|
|
2555
|
+
});
|
|
2556
|
+
}
|
|
2103
2557
|
static configSchema() {
|
|
2104
2558
|
return fetchUrlTaskConfigSchema;
|
|
2105
2559
|
}
|
|
@@ -8799,7 +9253,7 @@ Workflow38.prototype.lambda = CreateWorkflow37(LambdaTask);
|
|
|
8799
9253
|
import {
|
|
8800
9254
|
CreateWorkflow as CreateWorkflow38,
|
|
8801
9255
|
Entitlements as Entitlements3,
|
|
8802
|
-
mergeEntitlements,
|
|
9256
|
+
mergeEntitlements as mergeEntitlements2,
|
|
8803
9257
|
Task as Task37,
|
|
8804
9258
|
Workflow as Workflow39
|
|
8805
9259
|
} from "@workglow/task-graph";
|
|
@@ -8994,13 +9448,13 @@ class McpListTask extends Task37 {
|
|
|
8994
9448
|
const base = McpListTask.entitlements();
|
|
8995
9449
|
const transport = getMcpServerTransport(this);
|
|
8996
9450
|
if (transport === "stdio") {
|
|
8997
|
-
return
|
|
9451
|
+
return mergeEntitlements2(base, {
|
|
8998
9452
|
entitlements: [
|
|
8999
9453
|
{ id: Entitlements3.MCP_STDIO, reason: "Uses stdio transport to spawn local process" }
|
|
9000
9454
|
]
|
|
9001
9455
|
});
|
|
9002
9456
|
}
|
|
9003
|
-
return
|
|
9457
|
+
return mergeEntitlements2(base, {
|
|
9004
9458
|
entitlements: [{ id: Entitlements3.NETWORK_HTTP, reason: "Connects to MCP server over HTTP" }]
|
|
9005
9459
|
});
|
|
9006
9460
|
}
|
|
@@ -9087,7 +9541,7 @@ Workflow39.prototype.mcpList = CreateWorkflow38(McpListTask);
|
|
|
9087
9541
|
import {
|
|
9088
9542
|
CreateWorkflow as CreateWorkflow39,
|
|
9089
9543
|
Entitlements as Entitlements4,
|
|
9090
|
-
mergeEntitlements as
|
|
9544
|
+
mergeEntitlements as mergeEntitlements3,
|
|
9091
9545
|
Task as Task38,
|
|
9092
9546
|
TaskConfigSchema as TaskConfigSchema8,
|
|
9093
9547
|
Workflow as Workflow40
|
|
@@ -9243,13 +9697,13 @@ class McpPromptGetTask extends Task38 {
|
|
|
9243
9697
|
const base = McpPromptGetTask.entitlements();
|
|
9244
9698
|
const transport = getMcpServerTransport(this);
|
|
9245
9699
|
if (transport === "stdio") {
|
|
9246
|
-
return
|
|
9700
|
+
return mergeEntitlements3(base, {
|
|
9247
9701
|
entitlements: [
|
|
9248
9702
|
{ id: Entitlements4.MCP_STDIO, reason: "Uses stdio transport to spawn local process" }
|
|
9249
9703
|
]
|
|
9250
9704
|
});
|
|
9251
9705
|
}
|
|
9252
|
-
return
|
|
9706
|
+
return mergeEntitlements3(base, {
|
|
9253
9707
|
entitlements: [
|
|
9254
9708
|
{ id: Entitlements4.NETWORK_HTTP, reason: "Connects to MCP server over HTTP" },
|
|
9255
9709
|
{ id: Entitlements4.CREDENTIAL, reason: "May require authentication", optional: true }
|
|
@@ -9353,7 +9807,7 @@ Workflow40.prototype.mcpPromptGet = CreateWorkflow39(McpPromptGetTask);
|
|
|
9353
9807
|
import {
|
|
9354
9808
|
CreateWorkflow as CreateWorkflow40,
|
|
9355
9809
|
Entitlements as Entitlements5,
|
|
9356
|
-
mergeEntitlements as
|
|
9810
|
+
mergeEntitlements as mergeEntitlements4,
|
|
9357
9811
|
Task as Task39,
|
|
9358
9812
|
TaskConfigSchema as TaskConfigSchema9,
|
|
9359
9813
|
Workflow as Workflow41
|
|
@@ -9422,13 +9876,13 @@ class McpResourceReadTask extends Task39 {
|
|
|
9422
9876
|
const base = McpResourceReadTask.entitlements();
|
|
9423
9877
|
const transport = getMcpServerTransport(this);
|
|
9424
9878
|
if (transport === "stdio") {
|
|
9425
|
-
return
|
|
9879
|
+
return mergeEntitlements4(base, {
|
|
9426
9880
|
entitlements: [
|
|
9427
9881
|
{ id: Entitlements5.MCP_STDIO, reason: "Uses stdio transport to spawn local process" }
|
|
9428
9882
|
]
|
|
9429
9883
|
});
|
|
9430
9884
|
}
|
|
9431
|
-
return
|
|
9885
|
+
return mergeEntitlements4(base, {
|
|
9432
9886
|
entitlements: [
|
|
9433
9887
|
{ id: Entitlements5.NETWORK_HTTP, reason: "Connects to MCP server over HTTP" },
|
|
9434
9888
|
{ id: Entitlements5.CREDENTIAL, reason: "May require authentication", optional: true }
|
|
@@ -9654,7 +10108,7 @@ Workflow42.prototype.mcpSearch = CreateWorkflow41(McpSearchTask);
|
|
|
9654
10108
|
import {
|
|
9655
10109
|
CreateWorkflow as CreateWorkflow42,
|
|
9656
10110
|
Entitlements as Entitlements7,
|
|
9657
|
-
mergeEntitlements as
|
|
10111
|
+
mergeEntitlements as mergeEntitlements5,
|
|
9658
10112
|
Task as Task41,
|
|
9659
10113
|
TaskConfigSchema as TaskConfigSchema10,
|
|
9660
10114
|
Workflow as Workflow43
|
|
@@ -9802,13 +10256,13 @@ class McpToolCallTask extends Task41 {
|
|
|
9802
10256
|
const base = McpToolCallTask.entitlements();
|
|
9803
10257
|
const transport = getMcpServerTransport(this);
|
|
9804
10258
|
if (transport === "stdio") {
|
|
9805
|
-
return
|
|
10259
|
+
return mergeEntitlements5(base, {
|
|
9806
10260
|
entitlements: [
|
|
9807
10261
|
{ id: Entitlements7.MCP_STDIO, reason: "Uses stdio transport to spawn local process" }
|
|
9808
10262
|
]
|
|
9809
10263
|
});
|
|
9810
10264
|
}
|
|
9811
|
-
return
|
|
10265
|
+
return mergeEntitlements5(base, {
|
|
9812
10266
|
entitlements: [
|
|
9813
10267
|
{ id: Entitlements7.NETWORK_HTTP, reason: "Connects to MCP server over HTTP" },
|
|
9814
10268
|
{ id: Entitlements7.CREDENTIAL, reason: "May require authentication", optional: true }
|
|
@@ -12099,18 +12553,24 @@ var registerCommonTasks2 = () => {
|
|
|
12099
12553
|
return [...tasks, FileLoaderTask2];
|
|
12100
12554
|
};
|
|
12101
12555
|
export {
|
|
12556
|
+
urlResourcePattern,
|
|
12557
|
+
tryNormalizeIPv4,
|
|
12102
12558
|
split,
|
|
12103
12559
|
setGlobalMcpServerRepository,
|
|
12104
12560
|
searchMcpRegistryPage,
|
|
12105
12561
|
searchMcpRegistry,
|
|
12562
|
+
safeFetch,
|
|
12106
12563
|
resolveImageInput,
|
|
12107
12564
|
resolveHumanConnector,
|
|
12108
12565
|
resolveAuthSecrets,
|
|
12566
|
+
resetSafeFetch,
|
|
12567
|
+
registerSafeFetch,
|
|
12109
12568
|
registerMcpTaskDeps,
|
|
12110
12569
|
registerMcpServer,
|
|
12111
12570
|
registerImageRasterCodec,
|
|
12112
12571
|
registerCommonTasks2 as registerCommonTasks,
|
|
12113
12572
|
produceImageOutput,
|
|
12573
|
+
normalizeOutputMimeType,
|
|
12114
12574
|
merge,
|
|
12115
12575
|
mcpTransportTypes,
|
|
12116
12576
|
mcpToolCall,
|
|
@@ -12128,6 +12588,7 @@ export {
|
|
|
12128
12588
|
json,
|
|
12129
12589
|
javaScript,
|
|
12130
12590
|
isDataUriImage,
|
|
12591
|
+
getSafeFetchImpl,
|
|
12131
12592
|
getMcpTaskDeps,
|
|
12132
12593
|
getMcpServerConfig,
|
|
12133
12594
|
getMcpServer,
|
|
@@ -12137,11 +12598,17 @@ export {
|
|
|
12137
12598
|
formatImageOutput,
|
|
12138
12599
|
fileLoader,
|
|
12139
12600
|
fetchUrl,
|
|
12601
|
+
extractDataUriMimeType,
|
|
12140
12602
|
delay,
|
|
12141
12603
|
debugLog,
|
|
12142
12604
|
createMcpClient,
|
|
12143
12605
|
createAuthProvider,
|
|
12606
|
+
classifyUrl,
|
|
12607
|
+
classifyIpLiteral,
|
|
12144
12608
|
buildAuthConfig,
|
|
12609
|
+
assertWithinPixelBudget,
|
|
12610
|
+
assertWithinByteBudget,
|
|
12611
|
+
assertIsDataUri,
|
|
12145
12612
|
VectorSumTask,
|
|
12146
12613
|
VectorSubtractTask,
|
|
12147
12614
|
VectorScaleTask,
|
|
@@ -12176,7 +12643,9 @@ export {
|
|
|
12176
12643
|
ScalarCeilTask,
|
|
12177
12644
|
ScalarAddTask,
|
|
12178
12645
|
ScalarAbsTask,
|
|
12646
|
+
SUPPORTED_OUTPUT_MIME_TYPES,
|
|
12179
12647
|
RegexTask,
|
|
12648
|
+
REJECTED_DECODE_MIME_TYPES,
|
|
12180
12649
|
OutputTask,
|
|
12181
12650
|
MergeTask,
|
|
12182
12651
|
McpToolCallTask,
|
|
@@ -12191,6 +12660,9 @@ export {
|
|
|
12191
12660
|
MCP_TASK_DEPS,
|
|
12192
12661
|
MCP_SERVER_REPOSITORY,
|
|
12193
12662
|
MCP_SERVERS,
|
|
12663
|
+
MAX_INPUT_BYTES_NODE,
|
|
12664
|
+
MAX_INPUT_BYTES_BROWSER,
|
|
12665
|
+
MAX_DECODED_PIXELS,
|
|
12194
12666
|
LambdaTask,
|
|
12195
12667
|
JsonTask,
|
|
12196
12668
|
JsonPathTask,
|
|
@@ -12231,4 +12703,4 @@ export {
|
|
|
12231
12703
|
ArrayTask
|
|
12232
12704
|
};
|
|
12233
12705
|
|
|
12234
|
-
//# debugId=
|
|
12706
|
+
//# debugId=4A1D9A65F809EEAD64756E2164756E21
|