@easynet/agent-tool-buildin 0.0.2 → 0.0.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/CoreToolsModule.d.ts +1 -1
- package/dist/CoreToolsModule.d.ts.map +1 -1
- package/dist/http/downloadFile.d.ts.map +1 -1
- package/dist/http/duckduckgoSearch.d.ts.map +1 -1
- package/dist/http/fetchJson.d.ts.map +1 -1
- package/dist/http/fetchPageMainContent.d.ts.map +1 -1
- package/dist/http/fetchText.d.ts.map +1 -1
- package/dist/http/head.d.ts.map +1 -1
- package/dist/http/yahooFinance.d.ts.map +1 -1
- package/dist/index.cjs +156 -306
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +130 -280
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +5 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/security/sandbox.d.ts +0 -9
- package/dist/security/sandbox.d.ts.map +0 -1
- package/dist/security/ssrf.d.ts +0 -12
- package/dist/security/ssrf.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -103,62 +103,19 @@ var DEFAULT_CORE_TOOLS_CONFIG = {
|
|
|
103
103
|
|
|
104
104
|
// fs/readText.ts
|
|
105
105
|
import { readFile, stat } from "fs/promises";
|
|
106
|
-
|
|
107
|
-
// security/sandbox.ts
|
|
108
|
-
import { resolve, normalize, dirname, basename } from "path";
|
|
109
|
-
import { realpath, access } from "fs/promises";
|
|
106
|
+
import { resolveSandboxedPath } from "@easynet/agent-tool";
|
|
110
107
|
import { createTaggedError } from "@easynet/agent-tool";
|
|
111
|
-
async function resolveSandboxedPath(inputPath, sandboxRoot) {
|
|
112
|
-
let normalizedRoot;
|
|
113
|
-
try {
|
|
114
|
-
normalizedRoot = await realpath(resolve(sandboxRoot));
|
|
115
|
-
} catch {
|
|
116
|
-
normalizedRoot = normalize(resolve(sandboxRoot));
|
|
117
|
-
}
|
|
118
|
-
const resolved = resolve(normalizedRoot, inputPath);
|
|
119
|
-
let real;
|
|
120
|
-
try {
|
|
121
|
-
await access(resolved);
|
|
122
|
-
real = await realpath(resolved);
|
|
123
|
-
} catch {
|
|
124
|
-
const parentDir = dirname(resolved);
|
|
125
|
-
let realParent;
|
|
126
|
-
try {
|
|
127
|
-
await access(parentDir);
|
|
128
|
-
realParent = await realpath(parentDir);
|
|
129
|
-
} catch {
|
|
130
|
-
realParent = normalize(parentDir);
|
|
131
|
-
}
|
|
132
|
-
real = resolve(realParent, basename(resolved));
|
|
133
|
-
}
|
|
134
|
-
if (!isWithinRoot(real, normalizedRoot)) {
|
|
135
|
-
throw createTaggedError(
|
|
136
|
-
"PATH_OUTSIDE_SANDBOX",
|
|
137
|
-
`Path "${inputPath}" resolves to "${real}" which is outside sandbox "${normalizedRoot}"`,
|
|
138
|
-
{ inputPath, resolvedPath: real, sandboxRoot: normalizedRoot }
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
return real;
|
|
142
|
-
}
|
|
143
|
-
function isWithinRoot(path, root) {
|
|
144
|
-
const normalizedPath = normalize(path);
|
|
145
|
-
const normalizedRoot = normalize(root);
|
|
146
|
-
return normalizedPath === normalizedRoot || normalizedPath.startsWith(normalizedRoot + "/");
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// fs/readText.ts
|
|
150
|
-
import { createTaggedError as createTaggedError2 } from "@easynet/agent-tool";
|
|
151
108
|
var readTextHandler = (async (args) => {
|
|
152
109
|
const ctx = getBuiltinContext();
|
|
153
110
|
const inputPath = (args.path ?? args.filePath)?.trim();
|
|
154
111
|
if (!inputPath) {
|
|
155
|
-
throw
|
|
112
|
+
throw createTaggedError("FS_INVALID", "path is required (pass 'path' or 'filePath')", {});
|
|
156
113
|
}
|
|
157
114
|
const maxBytes = args.maxBytes ?? ctx.config.maxReadBytes;
|
|
158
115
|
const resolvedPath = await resolveSandboxedPath(inputPath, ctx.config.sandboxRoot);
|
|
159
116
|
const fileStat = await stat(resolvedPath);
|
|
160
117
|
if (fileStat.size > maxBytes) {
|
|
161
|
-
throw
|
|
118
|
+
throw createTaggedError(
|
|
162
119
|
"FILE_TOO_LARGE",
|
|
163
120
|
`File size ${fileStat.size} bytes exceeds limit of ${maxBytes} bytes`,
|
|
164
121
|
{ path: resolvedPath, size: fileStat.size, limit: maxBytes }
|
|
@@ -185,7 +142,8 @@ var readTextHandler = (async (args) => {
|
|
|
185
142
|
// fs/writeText.ts
|
|
186
143
|
import { writeFile, mkdir } from "fs/promises";
|
|
187
144
|
import { createHash } from "crypto";
|
|
188
|
-
import { dirname
|
|
145
|
+
import { dirname } from "path";
|
|
146
|
+
import { resolveSandboxedPath as resolveSandboxedPath2 } from "@easynet/agent-tool";
|
|
189
147
|
var writeTextHandler = (async (args) => {
|
|
190
148
|
const ctx = getBuiltinContext();
|
|
191
149
|
const inputPath = (args.path ?? args.filePath)?.trim();
|
|
@@ -195,11 +153,11 @@ var writeTextHandler = (async (args) => {
|
|
|
195
153
|
const text = args.text ?? args.content ?? "";
|
|
196
154
|
const overwrite = args.overwrite ?? false;
|
|
197
155
|
const mkdirp = args.mkdirp ?? true;
|
|
198
|
-
const resolvedPath = await
|
|
156
|
+
const resolvedPath = await resolveSandboxedPath2(inputPath, ctx.config.sandboxRoot);
|
|
199
157
|
if (!overwrite) {
|
|
200
|
-
const { access
|
|
158
|
+
const { access } = await import("fs/promises");
|
|
201
159
|
try {
|
|
202
|
-
await
|
|
160
|
+
await access(resolvedPath);
|
|
203
161
|
throw new Error(
|
|
204
162
|
`File already exists: ${resolvedPath}. Set overwrite=true to allow overwriting.`
|
|
205
163
|
);
|
|
@@ -211,7 +169,7 @@ var writeTextHandler = (async (args) => {
|
|
|
211
169
|
}
|
|
212
170
|
}
|
|
213
171
|
if (mkdirp) {
|
|
214
|
-
await mkdir(
|
|
172
|
+
await mkdir(dirname(resolvedPath), { recursive: true });
|
|
215
173
|
}
|
|
216
174
|
await writeFile(resolvedPath, text, "utf-8");
|
|
217
175
|
const bytes = Buffer.byteLength(text, "utf-8");
|
|
@@ -235,7 +193,8 @@ var writeTextHandler = (async (args) => {
|
|
|
235
193
|
|
|
236
194
|
// fs/listDir.ts
|
|
237
195
|
import { readdir, stat as stat2 } from "fs/promises";
|
|
238
|
-
import { resolve
|
|
196
|
+
import { resolve, join } from "path";
|
|
197
|
+
import { resolveSandboxedPath as resolveSandboxedPath3 } from "@easynet/agent-tool";
|
|
239
198
|
var listDirHandler = (async (args) => {
|
|
240
199
|
const ctx = getBuiltinContext();
|
|
241
200
|
const inputPath = (args.path ?? args.filePath ?? args.dir ?? args.directory)?.trim();
|
|
@@ -246,7 +205,7 @@ var listDirHandler = (async (args) => {
|
|
|
246
205
|
const includeHidden = args.includeHidden ?? false;
|
|
247
206
|
const recursive = args.recursive ?? false;
|
|
248
207
|
const maxDepth = args.maxDepth ?? 5;
|
|
249
|
-
const resolvedPath = await
|
|
208
|
+
const resolvedPath = await resolveSandboxedPath3(inputPath, ctx.config.sandboxRoot);
|
|
250
209
|
const entries = [];
|
|
251
210
|
let truncated = false;
|
|
252
211
|
await walkDir(resolvedPath, "", entries, {
|
|
@@ -281,7 +240,7 @@ async function walkDir(basePath, relativePath, entries, options) {
|
|
|
281
240
|
options.onTruncate();
|
|
282
241
|
return;
|
|
283
242
|
}
|
|
284
|
-
const fullPath = relativePath ?
|
|
243
|
+
const fullPath = relativePath ? resolve(basePath, relativePath) : basePath;
|
|
285
244
|
const dirEntries = await readdir(fullPath, { withFileTypes: true });
|
|
286
245
|
for (const dirent of dirEntries) {
|
|
287
246
|
if (entries.length >= options.maxEntries) {
|
|
@@ -331,6 +290,7 @@ import { readdir as readdir2, stat as stat3 } from "fs/promises";
|
|
|
331
290
|
import { createReadStream } from "fs";
|
|
332
291
|
import { createInterface } from "readline";
|
|
333
292
|
import { join as join2, relative } from "path";
|
|
293
|
+
import { resolveSandboxedPath as resolveSandboxedPath4 } from "@easynet/agent-tool";
|
|
334
294
|
var searchTextHandler = (async (args) => {
|
|
335
295
|
const ctx = getBuiltinContext();
|
|
336
296
|
const rootPath = (args.root ?? args.path ?? args.dir ?? args.directory)?.trim();
|
|
@@ -344,7 +304,7 @@ var searchTextHandler = (async (args) => {
|
|
|
344
304
|
const glob = args.glob ?? "**/*.{md,txt,log,json,ts,js,py,java,scala}";
|
|
345
305
|
const maxMatches = args.maxMatches ?? 100;
|
|
346
306
|
const maxFiles = args.maxFiles ?? 500;
|
|
347
|
-
const resolvedRoot = await
|
|
307
|
+
const resolvedRoot = await resolveSandboxedPath4(rootPath, ctx.config.sandboxRoot);
|
|
348
308
|
let regex;
|
|
349
309
|
try {
|
|
350
310
|
regex = new RegExp(query, "i");
|
|
@@ -459,20 +419,21 @@ function escapeRegExp(str) {
|
|
|
459
419
|
import { createReadStream as createReadStream2 } from "fs";
|
|
460
420
|
import { stat as stat4 } from "fs/promises";
|
|
461
421
|
import { createHash as createHash2 } from "crypto";
|
|
462
|
-
import {
|
|
422
|
+
import { resolveSandboxedPath as resolveSandboxedPath5 } from "@easynet/agent-tool";
|
|
423
|
+
import { createTaggedError as createTaggedError2 } from "@easynet/agent-tool";
|
|
463
424
|
var sha256Handler = (async (args) => {
|
|
464
425
|
const ctx = getBuiltinContext();
|
|
465
426
|
const inputPath = (args.path ?? args.filePath)?.trim();
|
|
466
427
|
if (!inputPath) {
|
|
467
|
-
throw
|
|
428
|
+
throw createTaggedError2("FS_INVALID", "path is required (pass 'path' or 'filePath')", {});
|
|
468
429
|
}
|
|
469
|
-
const resolvedPath = await
|
|
430
|
+
const resolvedPath = await resolveSandboxedPath5(inputPath, ctx.config.sandboxRoot);
|
|
470
431
|
const fileStat = await stat4(resolvedPath);
|
|
471
|
-
const hash = await new Promise((
|
|
432
|
+
const hash = await new Promise((resolve2, reject) => {
|
|
472
433
|
const hasher = createHash2("sha256");
|
|
473
434
|
const stream = createReadStream2(resolvedPath);
|
|
474
435
|
stream.on("data", (chunk) => hasher.update(chunk));
|
|
475
|
-
stream.on("end", () =>
|
|
436
|
+
stream.on("end", () => resolve2(hasher.digest("hex")));
|
|
476
437
|
stream.on("error", reject);
|
|
477
438
|
});
|
|
478
439
|
return {
|
|
@@ -494,6 +455,7 @@ var sha256Handler = (async (args) => {
|
|
|
494
455
|
|
|
495
456
|
// fs/deletePath.ts
|
|
496
457
|
import { rm, unlink, rmdir, stat as stat5 } from "fs/promises";
|
|
458
|
+
import { resolveSandboxedPath as resolveSandboxedPath6 } from "@easynet/agent-tool";
|
|
497
459
|
var deletePathHandler = (async (args) => {
|
|
498
460
|
const ctx = getBuiltinContext();
|
|
499
461
|
const inputPath = (args.path ?? args.filePath)?.trim();
|
|
@@ -507,7 +469,7 @@ var deletePathHandler = (async (args) => {
|
|
|
507
469
|
"Deletion not confirmed. Set confirm=true to proceed with deletion."
|
|
508
470
|
);
|
|
509
471
|
}
|
|
510
|
-
const resolvedPath = await
|
|
472
|
+
const resolvedPath = await resolveSandboxedPath6(inputPath, ctx.config.sandboxRoot);
|
|
511
473
|
let realSandboxRoot;
|
|
512
474
|
try {
|
|
513
475
|
const { realpath: rp } = await import("fs/promises");
|
|
@@ -546,172 +508,25 @@ var deletePathHandler = (async (args) => {
|
|
|
546
508
|
};
|
|
547
509
|
});
|
|
548
510
|
|
|
549
|
-
// security/ssrf.ts
|
|
550
|
-
import { lookup } from "dns/promises";
|
|
551
|
-
import { createTaggedError as createTaggedError4 } from "@easynet/agent-tool";
|
|
552
|
-
async function validateUrl(url, allowedHosts, blockedCidrs) {
|
|
553
|
-
let parsed;
|
|
554
|
-
try {
|
|
555
|
-
parsed = new URL(url);
|
|
556
|
-
} catch {
|
|
557
|
-
throw createTaggedError4(
|
|
558
|
-
"HTTP_DISALLOWED_HOST",
|
|
559
|
-
`Invalid URL: ${url}`,
|
|
560
|
-
{ url }
|
|
561
|
-
);
|
|
562
|
-
}
|
|
563
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
564
|
-
throw createTaggedError4(
|
|
565
|
-
"HTTP_DISALLOWED_HOST",
|
|
566
|
-
`Protocol not allowed: ${parsed.protocol}. Only http: and https: are supported.`,
|
|
567
|
-
{ url, protocol: parsed.protocol }
|
|
568
|
-
);
|
|
569
|
-
}
|
|
570
|
-
const hostname = parsed.hostname;
|
|
571
|
-
if (!isHostAllowed(hostname, allowedHosts)) {
|
|
572
|
-
throw createTaggedError4(
|
|
573
|
-
"HTTP_DISALLOWED_HOST",
|
|
574
|
-
`Host "${hostname}" is not in the allowed hosts list`,
|
|
575
|
-
{ url, hostname, allowedHosts }
|
|
576
|
-
);
|
|
577
|
-
}
|
|
578
|
-
try {
|
|
579
|
-
const { address } = await lookup(hostname);
|
|
580
|
-
if (isIpInBlockedCidrs(address, blockedCidrs)) {
|
|
581
|
-
throw createTaggedError4(
|
|
582
|
-
"HTTP_DISALLOWED_HOST",
|
|
583
|
-
`Host "${hostname}" resolves to blocked IP: ${address}`,
|
|
584
|
-
{ url, hostname, resolvedIp: address }
|
|
585
|
-
);
|
|
586
|
-
}
|
|
587
|
-
} catch (err) {
|
|
588
|
-
if (err instanceof Error && err.kind === "HTTP_DISALLOWED_HOST") {
|
|
589
|
-
throw err;
|
|
590
|
-
}
|
|
591
|
-
throw createTaggedError4(
|
|
592
|
-
"HTTP_DISALLOWED_HOST",
|
|
593
|
-
`DNS resolution failed for host "${hostname}": ${err instanceof Error ? err.message : String(err)}`,
|
|
594
|
-
{ url, hostname }
|
|
595
|
-
);
|
|
596
|
-
}
|
|
597
|
-
return parsed;
|
|
598
|
-
}
|
|
599
|
-
function isHostAllowed(hostname, allowedHosts) {
|
|
600
|
-
for (const pattern of allowedHosts) {
|
|
601
|
-
if (pattern.startsWith("*.")) {
|
|
602
|
-
const suffix = pattern.slice(1);
|
|
603
|
-
if (hostname.endsWith(suffix) || hostname === pattern.slice(2)) {
|
|
604
|
-
return true;
|
|
605
|
-
}
|
|
606
|
-
} else if (hostname === pattern) {
|
|
607
|
-
return true;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
return false;
|
|
611
|
-
}
|
|
612
|
-
function isIpInBlockedCidrs(ip, cidrs) {
|
|
613
|
-
const normalizedIp = normalizeIp(ip);
|
|
614
|
-
if (!normalizedIp) return false;
|
|
615
|
-
for (const cidr of cidrs) {
|
|
616
|
-
if (cidr.includes(":")) {
|
|
617
|
-
if (!ip.includes(":")) continue;
|
|
618
|
-
if (isIpv6InCidr(ip, cidr)) return true;
|
|
619
|
-
} else {
|
|
620
|
-
if (isIpv4InCidr(normalizedIp, cidr)) return true;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
return false;
|
|
624
|
-
}
|
|
625
|
-
function normalizeIp(ip) {
|
|
626
|
-
if (ip.startsWith("::ffff:")) {
|
|
627
|
-
return ip.slice(7);
|
|
628
|
-
}
|
|
629
|
-
if (/^\d+\.\d+\.\d+\.\d+$/.test(ip)) {
|
|
630
|
-
return ip;
|
|
631
|
-
}
|
|
632
|
-
return null;
|
|
633
|
-
}
|
|
634
|
-
function isIpv4InCidr(ip, cidr) {
|
|
635
|
-
const [cidrIp, prefixStr] = cidr.split("/");
|
|
636
|
-
if (!cidrIp || !prefixStr) return false;
|
|
637
|
-
const prefix = parseInt(prefixStr, 10);
|
|
638
|
-
if (isNaN(prefix) || prefix < 0 || prefix > 32) return false;
|
|
639
|
-
const ipNum = ipv4ToNum(ip);
|
|
640
|
-
const cidrNum = ipv4ToNum(cidrIp);
|
|
641
|
-
if (ipNum === null || cidrNum === null) return false;
|
|
642
|
-
const mask = prefix === 0 ? 0 : ~0 << 32 - prefix >>> 0;
|
|
643
|
-
return (ipNum & mask) === (cidrNum & mask);
|
|
644
|
-
}
|
|
645
|
-
function ipv4ToNum(ip) {
|
|
646
|
-
const parts = ip.split(".");
|
|
647
|
-
if (parts.length !== 4) return null;
|
|
648
|
-
let num = 0;
|
|
649
|
-
for (const part of parts) {
|
|
650
|
-
const n = parseInt(part, 10);
|
|
651
|
-
if (isNaN(n) || n < 0 || n > 255) return null;
|
|
652
|
-
num = num << 8 | n;
|
|
653
|
-
}
|
|
654
|
-
return num >>> 0;
|
|
655
|
-
}
|
|
656
|
-
function isIpv6InCidr(ip, cidr) {
|
|
657
|
-
const [cidrIp, prefixStr] = cidr.split("/");
|
|
658
|
-
if (!cidrIp || !prefixStr) return false;
|
|
659
|
-
const prefix = parseInt(prefixStr, 10);
|
|
660
|
-
if (isNaN(prefix)) return false;
|
|
661
|
-
const ipBytes = expandIpv6(ip);
|
|
662
|
-
const cidrBytes = expandIpv6(cidrIp);
|
|
663
|
-
if (!ipBytes || !cidrBytes) return false;
|
|
664
|
-
const fullBytes = Math.floor(prefix / 8);
|
|
665
|
-
for (let i = 0; i < fullBytes && i < 16; i++) {
|
|
666
|
-
if (ipBytes[i] !== cidrBytes[i]) return false;
|
|
667
|
-
}
|
|
668
|
-
const remainingBits = prefix % 8;
|
|
669
|
-
if (remainingBits > 0 && fullBytes < 16) {
|
|
670
|
-
const mask = ~0 << 8 - remainingBits & 255;
|
|
671
|
-
if ((ipBytes[fullBytes] & mask) !== (cidrBytes[fullBytes] & mask)) return false;
|
|
672
|
-
}
|
|
673
|
-
return true;
|
|
674
|
-
}
|
|
675
|
-
function expandIpv6(ip) {
|
|
676
|
-
const zoneIdx = ip.indexOf("%");
|
|
677
|
-
if (zoneIdx !== -1) ip = ip.slice(0, zoneIdx);
|
|
678
|
-
const parts = ip.split("::");
|
|
679
|
-
if (parts.length > 2) return null;
|
|
680
|
-
const bytes = new Array(16).fill(0);
|
|
681
|
-
const expandGroup = (group) => {
|
|
682
|
-
if (!group) return [];
|
|
683
|
-
return group.split(":").flatMap((hex) => {
|
|
684
|
-
const val = parseInt(hex || "0", 16);
|
|
685
|
-
return [val >> 8 & 255, val & 255];
|
|
686
|
-
});
|
|
687
|
-
};
|
|
688
|
-
if (parts.length === 1) {
|
|
689
|
-
const expanded = expandGroup(parts[0]);
|
|
690
|
-
if (expanded.length !== 16) return null;
|
|
691
|
-
return expanded;
|
|
692
|
-
}
|
|
693
|
-
const left = expandGroup(parts[0]);
|
|
694
|
-
const right = expandGroup(parts[1]);
|
|
695
|
-
if (left.length + right.length > 16) return null;
|
|
696
|
-
for (let i = 0; i < left.length; i++) bytes[i] = left[i];
|
|
697
|
-
for (let i = 0; i < right.length; i++) bytes[16 - right.length + i] = right[i];
|
|
698
|
-
return bytes;
|
|
699
|
-
}
|
|
700
|
-
|
|
701
511
|
// http/fetchText.ts
|
|
702
|
-
import {
|
|
512
|
+
import { validateUrl } from "@easynet/agent-tool";
|
|
513
|
+
import { createTaggedError as createTaggedError3 } from "@easynet/agent-tool";
|
|
703
514
|
var fetchTextHandler = (async (args) => {
|
|
704
515
|
const ctx = getBuiltinContext();
|
|
705
516
|
const url = (args.url ?? args.uri)?.trim();
|
|
706
517
|
if (!url) {
|
|
707
|
-
throw
|
|
518
|
+
throw createTaggedError3("HTTP_INVALID", "url is required (pass 'url' or 'uri')", {});
|
|
708
519
|
}
|
|
709
520
|
const method = args.method ?? "GET";
|
|
710
521
|
const headers = args.headers ?? {};
|
|
711
522
|
const body = args.body ?? void 0;
|
|
712
523
|
const timeoutMs = args.timeoutMs ?? ctx.config.defaultTimeoutMs;
|
|
713
524
|
const maxBytes = args.maxBytes ?? ctx.config.maxHttpBytes;
|
|
714
|
-
await validateUrl(url,
|
|
525
|
+
await validateUrl(url, {
|
|
526
|
+
allowedHosts: ctx.config.allowedHosts,
|
|
527
|
+
blockedHosts: ctx.config.blockedHosts,
|
|
528
|
+
blockedCidrs: ctx.config.blockedCidrs
|
|
529
|
+
});
|
|
715
530
|
if (!headers["User-Agent"] && !headers["user-agent"]) {
|
|
716
531
|
headers["User-Agent"] = ctx.config.httpUserAgent;
|
|
717
532
|
}
|
|
@@ -727,13 +542,13 @@ var fetchTextHandler = (async (args) => {
|
|
|
727
542
|
});
|
|
728
543
|
} catch (err) {
|
|
729
544
|
if (err instanceof Error && err.name === "AbortError") {
|
|
730
|
-
throw
|
|
545
|
+
throw createTaggedError3(
|
|
731
546
|
"HTTP_TIMEOUT",
|
|
732
547
|
`Request to ${url} timed out after ${timeoutMs}ms`,
|
|
733
548
|
{ url, timeoutMs }
|
|
734
549
|
);
|
|
735
550
|
}
|
|
736
|
-
throw
|
|
551
|
+
throw createTaggedError3(
|
|
737
552
|
"UPSTREAM_ERROR",
|
|
738
553
|
`Fetch failed for ${url}: ${err instanceof Error ? err.message : String(err)}`,
|
|
739
554
|
{ url }
|
|
@@ -743,7 +558,7 @@ var fetchTextHandler = (async (args) => {
|
|
|
743
558
|
}
|
|
744
559
|
const contentLength = response.headers.get("content-length");
|
|
745
560
|
if (contentLength && parseInt(contentLength, 10) > maxBytes) {
|
|
746
|
-
throw
|
|
561
|
+
throw createTaggedError3(
|
|
747
562
|
"HTTP_TOO_LARGE",
|
|
748
563
|
`Response Content-Length ${contentLength} exceeds limit of ${maxBytes} bytes`,
|
|
749
564
|
{ url, contentLength: parseInt(contentLength, 10), limit: maxBytes }
|
|
@@ -788,7 +603,7 @@ async function readResponseWithLimit(response, maxBytes, url) {
|
|
|
788
603
|
totalBytes += value.byteLength;
|
|
789
604
|
if (totalBytes > maxBytes) {
|
|
790
605
|
reader.cancel();
|
|
791
|
-
throw
|
|
606
|
+
throw createTaggedError3(
|
|
792
607
|
"HTTP_TOO_LARGE",
|
|
793
608
|
`Response body exceeded limit of ${maxBytes} bytes while reading from ${url}`,
|
|
794
609
|
{ url, bytesRead: totalBytes, limit: maxBytes }
|
|
@@ -804,19 +619,24 @@ async function readResponseWithLimit(response, maxBytes, url) {
|
|
|
804
619
|
}
|
|
805
620
|
|
|
806
621
|
// http/fetchJson.ts
|
|
807
|
-
import {
|
|
622
|
+
import { validateUrl as validateUrl2 } from "@easynet/agent-tool";
|
|
623
|
+
import { createTaggedError as createTaggedError4 } from "@easynet/agent-tool";
|
|
808
624
|
var fetchJsonHandler = (async (args) => {
|
|
809
625
|
const ctx = getBuiltinContext();
|
|
810
626
|
const url = (args.url ?? args.uri)?.trim();
|
|
811
627
|
if (!url) {
|
|
812
|
-
throw
|
|
628
|
+
throw createTaggedError4("HTTP_INVALID", "url is required (pass 'url' or 'uri')", {});
|
|
813
629
|
}
|
|
814
630
|
const method = args.method ?? "GET";
|
|
815
631
|
const headers = args.headers ?? {};
|
|
816
632
|
const body = args.body ?? void 0;
|
|
817
633
|
const timeoutMs = args.timeoutMs ?? ctx.config.defaultTimeoutMs;
|
|
818
634
|
const maxBytes = args.maxBytes ?? ctx.config.maxHttpBytes;
|
|
819
|
-
await
|
|
635
|
+
await validateUrl2(url, {
|
|
636
|
+
allowedHosts: ctx.config.allowedHosts,
|
|
637
|
+
blockedHosts: ctx.config.blockedHosts,
|
|
638
|
+
blockedCidrs: ctx.config.blockedCidrs
|
|
639
|
+
});
|
|
820
640
|
if (!headers["Accept"] && !headers["accept"]) {
|
|
821
641
|
headers["Accept"] = "application/json";
|
|
822
642
|
}
|
|
@@ -835,13 +655,13 @@ var fetchJsonHandler = (async (args) => {
|
|
|
835
655
|
});
|
|
836
656
|
} catch (err) {
|
|
837
657
|
if (err instanceof Error && err.name === "AbortError") {
|
|
838
|
-
throw
|
|
658
|
+
throw createTaggedError4(
|
|
839
659
|
"HTTP_TIMEOUT",
|
|
840
660
|
`Request to ${url} timed out after ${timeoutMs}ms`,
|
|
841
661
|
{ url, timeoutMs }
|
|
842
662
|
);
|
|
843
663
|
}
|
|
844
|
-
throw
|
|
664
|
+
throw createTaggedError4(
|
|
845
665
|
"UPSTREAM_ERROR",
|
|
846
666
|
`Fetch failed for ${url}: ${err instanceof Error ? err.message : String(err)}`,
|
|
847
667
|
{ url }
|
|
@@ -851,7 +671,7 @@ var fetchJsonHandler = (async (args) => {
|
|
|
851
671
|
}
|
|
852
672
|
const contentLength = response.headers.get("content-length");
|
|
853
673
|
if (contentLength && parseInt(contentLength, 10) > maxBytes) {
|
|
854
|
-
throw
|
|
674
|
+
throw createTaggedError4(
|
|
855
675
|
"HTTP_TOO_LARGE",
|
|
856
676
|
`Response Content-Length ${contentLength} exceeds limit of ${maxBytes} bytes`,
|
|
857
677
|
{ url, contentLength: parseInt(contentLength, 10), limit: maxBytes }
|
|
@@ -860,7 +680,7 @@ var fetchJsonHandler = (async (args) => {
|
|
|
860
680
|
const text = await response.text();
|
|
861
681
|
const bytes = Buffer.byteLength(text, "utf-8");
|
|
862
682
|
if (bytes > maxBytes) {
|
|
863
|
-
throw
|
|
683
|
+
throw createTaggedError4(
|
|
864
684
|
"HTTP_TOO_LARGE",
|
|
865
685
|
`Response body ${bytes} bytes exceeds limit of ${maxBytes} bytes`,
|
|
866
686
|
{ url, bytes, limit: maxBytes }
|
|
@@ -870,7 +690,7 @@ var fetchJsonHandler = (async (args) => {
|
|
|
870
690
|
try {
|
|
871
691
|
json = JSON.parse(text);
|
|
872
692
|
} catch {
|
|
873
|
-
throw
|
|
693
|
+
throw createTaggedError4(
|
|
874
694
|
"UPSTREAM_ERROR",
|
|
875
695
|
`Failed to parse JSON response from ${url}: ${text.slice(0, 200)}`,
|
|
876
696
|
{ url, status: response.status, textPreview: text.slice(0, 500) }
|
|
@@ -897,28 +717,33 @@ var fetchJsonHandler = (async (args) => {
|
|
|
897
717
|
// http/downloadFile.ts
|
|
898
718
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
899
719
|
import { createHash as createHash3 } from "crypto";
|
|
900
|
-
import { dirname as
|
|
901
|
-
import {
|
|
720
|
+
import { dirname as dirname2 } from "path";
|
|
721
|
+
import { validateUrl as validateUrl3, resolveSandboxedPath as resolveSandboxedPath7 } from "@easynet/agent-tool";
|
|
722
|
+
import { createTaggedError as createTaggedError5 } from "@easynet/agent-tool";
|
|
902
723
|
var downloadFileHandler = (async (args) => {
|
|
903
724
|
const ctx = getBuiltinContext();
|
|
904
725
|
const url = (args.url ?? args.uri)?.trim();
|
|
905
726
|
if (!url) {
|
|
906
|
-
throw
|
|
727
|
+
throw createTaggedError5("HTTP_INVALID", "url is required (pass 'url' or 'uri')", {});
|
|
907
728
|
}
|
|
908
729
|
const destPath = (args.destPath ?? args.destination ?? args.filePath)?.trim();
|
|
909
730
|
if (!destPath) {
|
|
910
|
-
throw
|
|
731
|
+
throw createTaggedError5("HTTP_INVALID", "destPath is required (pass 'destPath', 'destination', or 'filePath')", {});
|
|
911
732
|
}
|
|
912
733
|
const headers = args.headers ?? {};
|
|
913
734
|
const timeoutMs = args.timeoutMs ?? ctx.config.defaultTimeoutMs;
|
|
914
735
|
const maxBytes = args.maxBytes ?? ctx.config.maxDownloadBytes;
|
|
915
736
|
const overwrite = args.overwrite ?? false;
|
|
916
|
-
await
|
|
917
|
-
|
|
737
|
+
await validateUrl3(url, {
|
|
738
|
+
allowedHosts: ctx.config.allowedHosts,
|
|
739
|
+
blockedHosts: ctx.config.blockedHosts,
|
|
740
|
+
blockedCidrs: ctx.config.blockedCidrs
|
|
741
|
+
});
|
|
742
|
+
const resolvedDest = await resolveSandboxedPath7(destPath, ctx.config.sandboxRoot);
|
|
918
743
|
if (!overwrite) {
|
|
919
|
-
const { access
|
|
744
|
+
const { access } = await import("fs/promises");
|
|
920
745
|
try {
|
|
921
|
-
await
|
|
746
|
+
await access(resolvedDest);
|
|
922
747
|
throw new Error(
|
|
923
748
|
`File already exists: ${resolvedDest}. Set overwrite=true to allow overwriting.`
|
|
924
749
|
);
|
|
@@ -943,13 +768,13 @@ var downloadFileHandler = (async (args) => {
|
|
|
943
768
|
});
|
|
944
769
|
} catch (err) {
|
|
945
770
|
if (err instanceof Error && err.name === "AbortError") {
|
|
946
|
-
throw
|
|
771
|
+
throw createTaggedError5(
|
|
947
772
|
"HTTP_TIMEOUT",
|
|
948
773
|
`Download from ${url} timed out after ${timeoutMs}ms`,
|
|
949
774
|
{ url, timeoutMs }
|
|
950
775
|
);
|
|
951
776
|
}
|
|
952
|
-
throw
|
|
777
|
+
throw createTaggedError5(
|
|
953
778
|
"UPSTREAM_ERROR",
|
|
954
779
|
`Download failed for ${url}: ${err instanceof Error ? err.message : String(err)}`,
|
|
955
780
|
{ url }
|
|
@@ -959,14 +784,14 @@ var downloadFileHandler = (async (args) => {
|
|
|
959
784
|
}
|
|
960
785
|
const contentLength = response.headers.get("content-length");
|
|
961
786
|
if (contentLength && parseInt(contentLength, 10) > maxBytes) {
|
|
962
|
-
throw
|
|
787
|
+
throw createTaggedError5(
|
|
963
788
|
"HTTP_TOO_LARGE",
|
|
964
789
|
`Download Content-Length ${contentLength} exceeds limit of ${maxBytes} bytes`,
|
|
965
790
|
{ url, contentLength: parseInt(contentLength, 10), limit: maxBytes }
|
|
966
791
|
);
|
|
967
792
|
}
|
|
968
793
|
if (!response.body) {
|
|
969
|
-
throw
|
|
794
|
+
throw createTaggedError5("UPSTREAM_ERROR", `No response body from ${url}`, { url });
|
|
970
795
|
}
|
|
971
796
|
const reader = response.body.getReader();
|
|
972
797
|
const chunks = [];
|
|
@@ -979,7 +804,7 @@ var downloadFileHandler = (async (args) => {
|
|
|
979
804
|
totalBytes += value.byteLength;
|
|
980
805
|
if (totalBytes > maxBytes) {
|
|
981
806
|
reader.cancel();
|
|
982
|
-
throw
|
|
807
|
+
throw createTaggedError5(
|
|
983
808
|
"HTTP_TOO_LARGE",
|
|
984
809
|
`Download from ${url} exceeded limit of ${maxBytes} bytes (received ${totalBytes})`,
|
|
985
810
|
{ url, bytesRead: totalBytes, limit: maxBytes }
|
|
@@ -992,7 +817,7 @@ var downloadFileHandler = (async (args) => {
|
|
|
992
817
|
reader.releaseLock();
|
|
993
818
|
}
|
|
994
819
|
const sha256 = hasher.digest("hex");
|
|
995
|
-
await mkdir2(
|
|
820
|
+
await mkdir2(dirname2(resolvedDest), { recursive: true });
|
|
996
821
|
const buffer = Buffer.concat(chunks);
|
|
997
822
|
await writeFile2(resolvedDest, buffer);
|
|
998
823
|
return {
|
|
@@ -1021,16 +846,21 @@ var downloadFileHandler = (async (args) => {
|
|
|
1021
846
|
});
|
|
1022
847
|
|
|
1023
848
|
// http/head.ts
|
|
1024
|
-
import {
|
|
849
|
+
import { validateUrl as validateUrl4 } from "@easynet/agent-tool";
|
|
850
|
+
import { createTaggedError as createTaggedError6 } from "@easynet/agent-tool";
|
|
1025
851
|
var headHandler = (async (args) => {
|
|
1026
852
|
const ctx = getBuiltinContext();
|
|
1027
853
|
const url = (args.url ?? args.uri)?.trim();
|
|
1028
854
|
if (!url) {
|
|
1029
|
-
throw
|
|
855
|
+
throw createTaggedError6("HTTP_INVALID", "url is required (pass 'url' or 'uri')", {});
|
|
1030
856
|
}
|
|
1031
857
|
const headers = args.headers ?? {};
|
|
1032
858
|
const timeoutMs = args.timeoutMs ?? ctx.config.defaultTimeoutMs;
|
|
1033
|
-
await
|
|
859
|
+
await validateUrl4(url, {
|
|
860
|
+
allowedHosts: ctx.config.allowedHosts,
|
|
861
|
+
blockedHosts: ctx.config.blockedHosts,
|
|
862
|
+
blockedCidrs: ctx.config.blockedCidrs
|
|
863
|
+
});
|
|
1034
864
|
if (!headers["User-Agent"] && !headers["user-agent"]) {
|
|
1035
865
|
headers["User-Agent"] = ctx.config.httpUserAgent;
|
|
1036
866
|
}
|
|
@@ -1045,13 +875,13 @@ var headHandler = (async (args) => {
|
|
|
1045
875
|
});
|
|
1046
876
|
} catch (err) {
|
|
1047
877
|
if (err instanceof Error && err.name === "AbortError") {
|
|
1048
|
-
throw
|
|
878
|
+
throw createTaggedError6(
|
|
1049
879
|
"HTTP_TIMEOUT",
|
|
1050
880
|
`HEAD request to ${url} timed out after ${timeoutMs}ms`,
|
|
1051
881
|
{ url, timeoutMs }
|
|
1052
882
|
);
|
|
1053
883
|
}
|
|
1054
|
-
throw
|
|
884
|
+
throw createTaggedError6(
|
|
1055
885
|
"UPSTREAM_ERROR",
|
|
1056
886
|
`HEAD request failed for ${url}: ${err instanceof Error ? err.message : String(err)}`,
|
|
1057
887
|
{ url }
|
|
@@ -1081,18 +911,23 @@ var headHandler = (async (args) => {
|
|
|
1081
911
|
});
|
|
1082
912
|
|
|
1083
913
|
// http/duckduckgoSearch.ts
|
|
1084
|
-
import {
|
|
914
|
+
import { validateUrl as validateUrl5 } from "@easynet/agent-tool";
|
|
915
|
+
import { createTaggedError as createTaggedError7 } from "@easynet/agent-tool";
|
|
1085
916
|
var DUCKDUCKGO_API = "https://api.duckduckgo.com/";
|
|
1086
917
|
var duckduckgoSearchHandler = (async (args) => {
|
|
1087
918
|
const ctx = getBuiltinContext();
|
|
1088
919
|
const query = (args.query ?? args.q)?.trim();
|
|
1089
920
|
if (!query) {
|
|
1090
|
-
throw
|
|
921
|
+
throw createTaggedError7("DUCKDUCKGO_INVALID", "query is required (pass 'query' or 'q')", {});
|
|
1091
922
|
}
|
|
1092
923
|
const timeoutMs = args.timeoutMs ?? ctx.config.defaultTimeoutMs;
|
|
1093
924
|
const maxResults = args.maxResults ?? 10;
|
|
1094
925
|
const url = `${DUCKDUCKGO_API}?q=${encodeURIComponent(query)}&format=json`;
|
|
1095
|
-
await
|
|
926
|
+
await validateUrl5(url, {
|
|
927
|
+
allowedHosts: ctx.config.allowedHosts,
|
|
928
|
+
blockedHosts: ctx.config.blockedHosts,
|
|
929
|
+
blockedCidrs: ctx.config.blockedCidrs
|
|
930
|
+
});
|
|
1096
931
|
const controller = new AbortController();
|
|
1097
932
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1098
933
|
let response;
|
|
@@ -1105,13 +940,13 @@ var duckduckgoSearchHandler = (async (args) => {
|
|
|
1105
940
|
} catch (err) {
|
|
1106
941
|
clearTimeout(timer);
|
|
1107
942
|
if (err instanceof Error && err.name === "AbortError") {
|
|
1108
|
-
throw
|
|
943
|
+
throw createTaggedError7(
|
|
1109
944
|
"HTTP_TIMEOUT",
|
|
1110
945
|
`DuckDuckGo search timed out after ${timeoutMs}ms`,
|
|
1111
946
|
{ query, timeoutMs }
|
|
1112
947
|
);
|
|
1113
948
|
}
|
|
1114
|
-
throw
|
|
949
|
+
throw createTaggedError7(
|
|
1115
950
|
"UPSTREAM_ERROR",
|
|
1116
951
|
`DuckDuckGo search failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1117
952
|
{ query }
|
|
@@ -1122,7 +957,7 @@ var duckduckgoSearchHandler = (async (args) => {
|
|
|
1122
957
|
const text = await response.text();
|
|
1123
958
|
const bytes = Buffer.byteLength(text, "utf-8");
|
|
1124
959
|
if (bytes > maxBytes) {
|
|
1125
|
-
throw
|
|
960
|
+
throw createTaggedError7(
|
|
1126
961
|
"HTTP_TOO_LARGE",
|
|
1127
962
|
`DuckDuckGo response ${bytes} bytes exceeds limit of ${maxBytes} bytes`,
|
|
1128
963
|
{ query, bytes, limit: maxBytes }
|
|
@@ -1132,7 +967,7 @@ var duckduckgoSearchHandler = (async (args) => {
|
|
|
1132
967
|
try {
|
|
1133
968
|
raw = JSON.parse(text);
|
|
1134
969
|
} catch {
|
|
1135
|
-
throw
|
|
970
|
+
throw createTaggedError7(
|
|
1136
971
|
"UPSTREAM_ERROR",
|
|
1137
972
|
`DuckDuckGo returned invalid JSON`,
|
|
1138
973
|
{ query, textPreview: text.slice(0, 200) }
|
|
@@ -1183,7 +1018,8 @@ var duckduckgoSearchHandler = (async (args) => {
|
|
|
1183
1018
|
|
|
1184
1019
|
// http/fetchPageMainContent.ts
|
|
1185
1020
|
import { parse } from "node-html-parser";
|
|
1186
|
-
import {
|
|
1021
|
+
import { validateUrl as validateUrl6 } from "@easynet/agent-tool";
|
|
1022
|
+
import { createTaggedError as createTaggedError8 } from "@easynet/agent-tool";
|
|
1187
1023
|
var MAIN_SELECTORS = [
|
|
1188
1024
|
"main",
|
|
1189
1025
|
"article",
|
|
@@ -1226,11 +1062,15 @@ var fetchPageMainContentHandler = (async (args) => {
|
|
|
1226
1062
|
const ctx = getBuiltinContext();
|
|
1227
1063
|
const url = (args.url ?? args.uri)?.trim();
|
|
1228
1064
|
if (!url) {
|
|
1229
|
-
throw
|
|
1065
|
+
throw createTaggedError8("HTTP_INVALID", "url is required (pass 'url' or 'uri')", {});
|
|
1230
1066
|
}
|
|
1231
1067
|
const timeoutMs = args.timeoutMs ?? ctx.config.defaultTimeoutMs;
|
|
1232
1068
|
const maxBytes = args.maxBytes ?? ctx.config.maxHttpBytes;
|
|
1233
|
-
await
|
|
1069
|
+
await validateUrl6(url, {
|
|
1070
|
+
allowedHosts: ctx.config.allowedHosts,
|
|
1071
|
+
blockedHosts: ctx.config.blockedHosts,
|
|
1072
|
+
blockedCidrs: ctx.config.blockedCidrs
|
|
1073
|
+
});
|
|
1234
1074
|
const controller = new AbortController();
|
|
1235
1075
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1236
1076
|
let response;
|
|
@@ -1243,13 +1083,13 @@ var fetchPageMainContentHandler = (async (args) => {
|
|
|
1243
1083
|
} catch (err) {
|
|
1244
1084
|
clearTimeout(timer);
|
|
1245
1085
|
if (err instanceof Error && err.name === "AbortError") {
|
|
1246
|
-
throw
|
|
1086
|
+
throw createTaggedError8(
|
|
1247
1087
|
"HTTP_TIMEOUT",
|
|
1248
1088
|
`Request to ${url} timed out after ${timeoutMs}ms`,
|
|
1249
1089
|
{ url, timeoutMs }
|
|
1250
1090
|
);
|
|
1251
1091
|
}
|
|
1252
|
-
throw
|
|
1092
|
+
throw createTaggedError8(
|
|
1253
1093
|
"UPSTREAM_ERROR",
|
|
1254
1094
|
`Fetch failed for ${url}: ${err instanceof Error ? err.message : String(err)}`,
|
|
1255
1095
|
{ url }
|
|
@@ -1258,7 +1098,7 @@ var fetchPageMainContentHandler = (async (args) => {
|
|
|
1258
1098
|
clearTimeout(timer);
|
|
1259
1099
|
const contentLength = response.headers.get("content-length");
|
|
1260
1100
|
if (contentLength && parseInt(contentLength, 10) > maxBytes) {
|
|
1261
|
-
throw
|
|
1101
|
+
throw createTaggedError8(
|
|
1262
1102
|
"HTTP_TOO_LARGE",
|
|
1263
1103
|
`Response Content-Length ${contentLength} exceeds limit of ${maxBytes} bytes`,
|
|
1264
1104
|
{ url, contentLength: parseInt(contentLength, 10), limit: maxBytes }
|
|
@@ -1267,7 +1107,7 @@ var fetchPageMainContentHandler = (async (args) => {
|
|
|
1267
1107
|
const rawText = await response.text();
|
|
1268
1108
|
const rawBytes = Buffer.byteLength(rawText, "utf-8");
|
|
1269
1109
|
if (rawBytes > maxBytes) {
|
|
1270
|
-
throw
|
|
1110
|
+
throw createTaggedError8(
|
|
1271
1111
|
"HTTP_TOO_LARGE",
|
|
1272
1112
|
`Response body ${rawBytes} bytes exceeds limit of ${maxBytes} bytes`,
|
|
1273
1113
|
{ url, bytes: rawBytes, limit: maxBytes }
|
|
@@ -1310,14 +1150,15 @@ var fetchPageMainContentHandler = (async (args) => {
|
|
|
1310
1150
|
});
|
|
1311
1151
|
|
|
1312
1152
|
// http/yahooFinance.ts
|
|
1313
|
-
import {
|
|
1153
|
+
import { validateUrl as validateUrl7 } from "@easynet/agent-tool";
|
|
1154
|
+
import { createTaggedError as createTaggedError9 } from "@easynet/agent-tool";
|
|
1314
1155
|
var YAHOO_CHART_BASE = "https://query1.finance.yahoo.com/v8/finance/chart/";
|
|
1315
1156
|
var yahooFinanceQuoteHandler = (async (args) => {
|
|
1316
1157
|
const ctx = getBuiltinContext();
|
|
1317
1158
|
const rawSymbols = args.symbols ?? args.symbol ?? args.tickers;
|
|
1318
1159
|
const symbols = Array.isArray(rawSymbols) ? rawSymbols.map((s) => String(s).trim().toUpperCase()).filter(Boolean) : rawSymbols != null ? [String(rawSymbols).trim().toUpperCase()].filter(Boolean) : [];
|
|
1319
1160
|
if (symbols.length === 0) {
|
|
1320
|
-
throw
|
|
1161
|
+
throw createTaggedError9(
|
|
1321
1162
|
"YAHOO_INVALID",
|
|
1322
1163
|
"At least one symbol is required",
|
|
1323
1164
|
{}
|
|
@@ -1329,7 +1170,11 @@ var yahooFinanceQuoteHandler = (async (args) => {
|
|
|
1329
1170
|
const quotes = [];
|
|
1330
1171
|
for (const symbol of symbols) {
|
|
1331
1172
|
const url2 = `${YAHOO_CHART_BASE}${encodeURIComponent(symbol)}?interval=1d&range=${range}`;
|
|
1332
|
-
await
|
|
1173
|
+
await validateUrl7(url2, {
|
|
1174
|
+
allowedHosts: ctx.config.allowedHosts,
|
|
1175
|
+
blockedHosts: ctx.config.blockedHosts,
|
|
1176
|
+
blockedCidrs: ctx.config.blockedCidrs
|
|
1177
|
+
});
|
|
1333
1178
|
const controller = new AbortController();
|
|
1334
1179
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1335
1180
|
let response;
|
|
@@ -1345,13 +1190,13 @@ var yahooFinanceQuoteHandler = (async (args) => {
|
|
|
1345
1190
|
} catch (err) {
|
|
1346
1191
|
clearTimeout(timer);
|
|
1347
1192
|
if (err instanceof Error && err.name === "AbortError") {
|
|
1348
|
-
throw
|
|
1193
|
+
throw createTaggedError9(
|
|
1349
1194
|
"HTTP_TIMEOUT",
|
|
1350
1195
|
`Yahoo Finance request for ${symbol} timed out after ${timeoutMs}ms`,
|
|
1351
1196
|
{ symbol, timeoutMs }
|
|
1352
1197
|
);
|
|
1353
1198
|
}
|
|
1354
|
-
throw
|
|
1199
|
+
throw createTaggedError9(
|
|
1355
1200
|
"UPSTREAM_ERROR",
|
|
1356
1201
|
`Yahoo Finance request failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1357
1202
|
{ symbol }
|
|
@@ -1361,7 +1206,7 @@ var yahooFinanceQuoteHandler = (async (args) => {
|
|
|
1361
1206
|
const text = await response.text();
|
|
1362
1207
|
const bytes = Buffer.byteLength(text, "utf-8");
|
|
1363
1208
|
if (bytes > maxBytes) {
|
|
1364
|
-
throw
|
|
1209
|
+
throw createTaggedError9(
|
|
1365
1210
|
"HTTP_TOO_LARGE",
|
|
1366
1211
|
`Yahoo Finance response ${bytes} bytes exceeds limit of ${maxBytes} bytes`,
|
|
1367
1212
|
{ symbol, bytes, limit: maxBytes }
|
|
@@ -1371,7 +1216,7 @@ var yahooFinanceQuoteHandler = (async (args) => {
|
|
|
1371
1216
|
try {
|
|
1372
1217
|
data = JSON.parse(text);
|
|
1373
1218
|
} catch {
|
|
1374
|
-
throw
|
|
1219
|
+
throw createTaggedError9(
|
|
1375
1220
|
"UPSTREAM_ERROR",
|
|
1376
1221
|
"Yahoo Finance returned invalid JSON",
|
|
1377
1222
|
{ symbol, textPreview: text.slice(0, 200) }
|
|
@@ -1379,7 +1224,7 @@ var yahooFinanceQuoteHandler = (async (args) => {
|
|
|
1379
1224
|
}
|
|
1380
1225
|
const error = data.chart?.error;
|
|
1381
1226
|
if (error?.description) {
|
|
1382
|
-
throw
|
|
1227
|
+
throw createTaggedError9(
|
|
1383
1228
|
"YAHOO_ERROR",
|
|
1384
1229
|
`Yahoo Finance error for ${symbol}: ${error.description}`,
|
|
1385
1230
|
{ symbol, code: error.code }
|
|
@@ -1592,12 +1437,13 @@ var templateRenderHandler = (async (args) => {
|
|
|
1592
1437
|
// exec/runCommand.ts
|
|
1593
1438
|
import { spawn } from "child_process";
|
|
1594
1439
|
import { resolve as pathResolve } from "path";
|
|
1595
|
-
import {
|
|
1440
|
+
import { resolveSandboxedPath as resolveSandboxedPath8 } from "@easynet/agent-tool";
|
|
1441
|
+
import { createTaggedError as createTaggedError10 } from "@easynet/agent-tool";
|
|
1596
1442
|
var runCommandHandler = (async (args) => {
|
|
1597
1443
|
const ctx = getBuiltinContext();
|
|
1598
1444
|
const { allowedCommands, maxCommandOutputBytes, commandTimeoutMs } = ctx.config;
|
|
1599
1445
|
if (!allowedCommands.length) {
|
|
1600
|
-
throw
|
|
1446
|
+
throw createTaggedError10(
|
|
1601
1447
|
"EXEC_DISABLED",
|
|
1602
1448
|
"Exec is disabled: allowedCommands is empty",
|
|
1603
1449
|
{}
|
|
@@ -1605,18 +1451,18 @@ var runCommandHandler = (async (args) => {
|
|
|
1605
1451
|
}
|
|
1606
1452
|
const rawCommand = (args.command ?? args.cmd)?.trim();
|
|
1607
1453
|
if (!rawCommand) {
|
|
1608
|
-
throw
|
|
1454
|
+
throw createTaggedError10("EXEC_INVALID", "command is required (pass 'command' or 'cmd')", {});
|
|
1609
1455
|
}
|
|
1610
1456
|
const baseName = rawCommand.replace(/^.*\//, "").trim();
|
|
1611
1457
|
if (baseName !== rawCommand || /[;&|$`\s]/.test(rawCommand)) {
|
|
1612
|
-
throw
|
|
1458
|
+
throw createTaggedError10(
|
|
1613
1459
|
"EXEC_INVALID",
|
|
1614
1460
|
"command must be a single executable name (no path, no shell chars)",
|
|
1615
1461
|
{ command: rawCommand }
|
|
1616
1462
|
);
|
|
1617
1463
|
}
|
|
1618
1464
|
if (!allowedCommands.includes(baseName)) {
|
|
1619
|
-
throw
|
|
1465
|
+
throw createTaggedError10(
|
|
1620
1466
|
"EXEC_NOT_ALLOWED",
|
|
1621
1467
|
`Command "${baseName}" is not in allowedCommands`,
|
|
1622
1468
|
{ command: baseName, allowed: allowedCommands }
|
|
@@ -1626,7 +1472,7 @@ var runCommandHandler = (async (args) => {
|
|
|
1626
1472
|
const timeoutMs = args.timeoutMs ?? commandTimeoutMs;
|
|
1627
1473
|
let cwd = pathResolve(ctx.config.sandboxRoot);
|
|
1628
1474
|
if (args.cwd != null && args.cwd !== "") {
|
|
1629
|
-
cwd = await
|
|
1475
|
+
cwd = await resolveSandboxedPath8(args.cwd, ctx.config.sandboxRoot);
|
|
1630
1476
|
}
|
|
1631
1477
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
1632
1478
|
const proc = spawn(baseName, cmdArgs, {
|
|
@@ -1643,7 +1489,7 @@ var runCommandHandler = (async (args) => {
|
|
|
1643
1489
|
if (totalBytes + len > maxCommandOutputBytes) {
|
|
1644
1490
|
proc.kill("SIGKILL");
|
|
1645
1491
|
rejectPromise(
|
|
1646
|
-
|
|
1492
|
+
createTaggedError10(
|
|
1647
1493
|
"EXEC_OUTPUT_TOO_LARGE",
|
|
1648
1494
|
`Command output exceeded ${maxCommandOutputBytes} bytes`,
|
|
1649
1495
|
{ maxBytes: maxCommandOutputBytes }
|
|
@@ -1681,7 +1527,7 @@ var runCommandHandler = (async (args) => {
|
|
|
1681
1527
|
proc.on("error", (err) => {
|
|
1682
1528
|
clearTimeout(timeout);
|
|
1683
1529
|
rejectPromise(
|
|
1684
|
-
|
|
1530
|
+
createTaggedError10("EXEC_SPAWN_ERROR", err.message, { command: baseName })
|
|
1685
1531
|
);
|
|
1686
1532
|
});
|
|
1687
1533
|
proc.on("close", (code, signal) => {
|
|
@@ -1915,7 +1761,8 @@ var CORE_GROUP_PREFIX = {
|
|
|
1915
1761
|
function registerCoreTools(registry, userConfig, options) {
|
|
1916
1762
|
const config = {
|
|
1917
1763
|
...DEFAULT_CORE_TOOLS_CONFIG,
|
|
1918
|
-
...userConfig
|
|
1764
|
+
...userConfig,
|
|
1765
|
+
blockedHosts: userConfig.blockedHosts ?? []
|
|
1919
1766
|
};
|
|
1920
1767
|
const adapter = new CoreAdapter(config);
|
|
1921
1768
|
const onlySet = options?.only?.length ? new Set(options.only) : null;
|
|
@@ -1932,6 +1779,9 @@ function registerCoreTools(registry, userConfig, options) {
|
|
|
1932
1779
|
}
|
|
1933
1780
|
return adapter;
|
|
1934
1781
|
}
|
|
1782
|
+
|
|
1783
|
+
// index.ts
|
|
1784
|
+
import { resolveSandboxedPath as resolveSandboxedPath9, validateUrl as validateUrl8, isIpInBlockedCidrs } from "@easynet/agent-tool";
|
|
1935
1785
|
export {
|
|
1936
1786
|
CoreAdapter,
|
|
1937
1787
|
DEFAULT_CORE_TOOLS_CONFIG,
|
|
@@ -1951,14 +1801,14 @@ export {
|
|
|
1951
1801
|
nowSpec,
|
|
1952
1802
|
readTextSpec,
|
|
1953
1803
|
registerCoreTools,
|
|
1954
|
-
resolveSandboxedPath,
|
|
1804
|
+
resolveSandboxedPath9 as resolveSandboxedPath,
|
|
1955
1805
|
runCommandSpec,
|
|
1956
1806
|
runWithBuiltinContext,
|
|
1957
1807
|
searchTextSpec,
|
|
1958
1808
|
sha256Spec,
|
|
1959
1809
|
templateRenderSpec,
|
|
1960
1810
|
truncateSpec,
|
|
1961
|
-
validateUrl,
|
|
1811
|
+
validateUrl8 as validateUrl,
|
|
1962
1812
|
writeTextSpec,
|
|
1963
1813
|
yahooFinanceQuoteSpec
|
|
1964
1814
|
};
|