@easynet/agent-tool-buildin 0.0.3 → 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 -309
- 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 -283
- 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,175 +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 === "*") {
|
|
602
|
-
return true;
|
|
603
|
-
}
|
|
604
|
-
if (pattern.startsWith("*.")) {
|
|
605
|
-
const suffix = pattern.slice(1);
|
|
606
|
-
if (hostname.endsWith(suffix) || hostname === pattern.slice(2)) {
|
|
607
|
-
return true;
|
|
608
|
-
}
|
|
609
|
-
} else if (hostname === pattern) {
|
|
610
|
-
return true;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
return false;
|
|
614
|
-
}
|
|
615
|
-
function isIpInBlockedCidrs(ip, cidrs) {
|
|
616
|
-
const normalizedIp = normalizeIp(ip);
|
|
617
|
-
if (!normalizedIp) return false;
|
|
618
|
-
for (const cidr of cidrs) {
|
|
619
|
-
if (cidr.includes(":")) {
|
|
620
|
-
if (!ip.includes(":")) continue;
|
|
621
|
-
if (isIpv6InCidr(ip, cidr)) return true;
|
|
622
|
-
} else {
|
|
623
|
-
if (isIpv4InCidr(normalizedIp, cidr)) return true;
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
return false;
|
|
627
|
-
}
|
|
628
|
-
function normalizeIp(ip) {
|
|
629
|
-
if (ip.startsWith("::ffff:")) {
|
|
630
|
-
return ip.slice(7);
|
|
631
|
-
}
|
|
632
|
-
if (/^\d+\.\d+\.\d+\.\d+$/.test(ip)) {
|
|
633
|
-
return ip;
|
|
634
|
-
}
|
|
635
|
-
return null;
|
|
636
|
-
}
|
|
637
|
-
function isIpv4InCidr(ip, cidr) {
|
|
638
|
-
const [cidrIp, prefixStr] = cidr.split("/");
|
|
639
|
-
if (!cidrIp || !prefixStr) return false;
|
|
640
|
-
const prefix = parseInt(prefixStr, 10);
|
|
641
|
-
if (isNaN(prefix) || prefix < 0 || prefix > 32) return false;
|
|
642
|
-
const ipNum = ipv4ToNum(ip);
|
|
643
|
-
const cidrNum = ipv4ToNum(cidrIp);
|
|
644
|
-
if (ipNum === null || cidrNum === null) return false;
|
|
645
|
-
const mask = prefix === 0 ? 0 : ~0 << 32 - prefix >>> 0;
|
|
646
|
-
return (ipNum & mask) === (cidrNum & mask);
|
|
647
|
-
}
|
|
648
|
-
function ipv4ToNum(ip) {
|
|
649
|
-
const parts = ip.split(".");
|
|
650
|
-
if (parts.length !== 4) return null;
|
|
651
|
-
let num = 0;
|
|
652
|
-
for (const part of parts) {
|
|
653
|
-
const n = parseInt(part, 10);
|
|
654
|
-
if (isNaN(n) || n < 0 || n > 255) return null;
|
|
655
|
-
num = num << 8 | n;
|
|
656
|
-
}
|
|
657
|
-
return num >>> 0;
|
|
658
|
-
}
|
|
659
|
-
function isIpv6InCidr(ip, cidr) {
|
|
660
|
-
const [cidrIp, prefixStr] = cidr.split("/");
|
|
661
|
-
if (!cidrIp || !prefixStr) return false;
|
|
662
|
-
const prefix = parseInt(prefixStr, 10);
|
|
663
|
-
if (isNaN(prefix)) return false;
|
|
664
|
-
const ipBytes = expandIpv6(ip);
|
|
665
|
-
const cidrBytes = expandIpv6(cidrIp);
|
|
666
|
-
if (!ipBytes || !cidrBytes) return false;
|
|
667
|
-
const fullBytes = Math.floor(prefix / 8);
|
|
668
|
-
for (let i = 0; i < fullBytes && i < 16; i++) {
|
|
669
|
-
if (ipBytes[i] !== cidrBytes[i]) return false;
|
|
670
|
-
}
|
|
671
|
-
const remainingBits = prefix % 8;
|
|
672
|
-
if (remainingBits > 0 && fullBytes < 16) {
|
|
673
|
-
const mask = ~0 << 8 - remainingBits & 255;
|
|
674
|
-
if ((ipBytes[fullBytes] & mask) !== (cidrBytes[fullBytes] & mask)) return false;
|
|
675
|
-
}
|
|
676
|
-
return true;
|
|
677
|
-
}
|
|
678
|
-
function expandIpv6(ip) {
|
|
679
|
-
const zoneIdx = ip.indexOf("%");
|
|
680
|
-
if (zoneIdx !== -1) ip = ip.slice(0, zoneIdx);
|
|
681
|
-
const parts = ip.split("::");
|
|
682
|
-
if (parts.length > 2) return null;
|
|
683
|
-
const bytes = new Array(16).fill(0);
|
|
684
|
-
const expandGroup = (group) => {
|
|
685
|
-
if (!group) return [];
|
|
686
|
-
return group.split(":").flatMap((hex) => {
|
|
687
|
-
const val = parseInt(hex || "0", 16);
|
|
688
|
-
return [val >> 8 & 255, val & 255];
|
|
689
|
-
});
|
|
690
|
-
};
|
|
691
|
-
if (parts.length === 1) {
|
|
692
|
-
const expanded = expandGroup(parts[0]);
|
|
693
|
-
if (expanded.length !== 16) return null;
|
|
694
|
-
return expanded;
|
|
695
|
-
}
|
|
696
|
-
const left = expandGroup(parts[0]);
|
|
697
|
-
const right = expandGroup(parts[1]);
|
|
698
|
-
if (left.length + right.length > 16) return null;
|
|
699
|
-
for (let i = 0; i < left.length; i++) bytes[i] = left[i];
|
|
700
|
-
for (let i = 0; i < right.length; i++) bytes[16 - right.length + i] = right[i];
|
|
701
|
-
return bytes;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
511
|
// http/fetchText.ts
|
|
705
|
-
import {
|
|
512
|
+
import { validateUrl } from "@easynet/agent-tool";
|
|
513
|
+
import { createTaggedError as createTaggedError3 } from "@easynet/agent-tool";
|
|
706
514
|
var fetchTextHandler = (async (args) => {
|
|
707
515
|
const ctx = getBuiltinContext();
|
|
708
516
|
const url = (args.url ?? args.uri)?.trim();
|
|
709
517
|
if (!url) {
|
|
710
|
-
throw
|
|
518
|
+
throw createTaggedError3("HTTP_INVALID", "url is required (pass 'url' or 'uri')", {});
|
|
711
519
|
}
|
|
712
520
|
const method = args.method ?? "GET";
|
|
713
521
|
const headers = args.headers ?? {};
|
|
714
522
|
const body = args.body ?? void 0;
|
|
715
523
|
const timeoutMs = args.timeoutMs ?? ctx.config.defaultTimeoutMs;
|
|
716
524
|
const maxBytes = args.maxBytes ?? ctx.config.maxHttpBytes;
|
|
717
|
-
await validateUrl(url,
|
|
525
|
+
await validateUrl(url, {
|
|
526
|
+
allowedHosts: ctx.config.allowedHosts,
|
|
527
|
+
blockedHosts: ctx.config.blockedHosts,
|
|
528
|
+
blockedCidrs: ctx.config.blockedCidrs
|
|
529
|
+
});
|
|
718
530
|
if (!headers["User-Agent"] && !headers["user-agent"]) {
|
|
719
531
|
headers["User-Agent"] = ctx.config.httpUserAgent;
|
|
720
532
|
}
|
|
@@ -730,13 +542,13 @@ var fetchTextHandler = (async (args) => {
|
|
|
730
542
|
});
|
|
731
543
|
} catch (err) {
|
|
732
544
|
if (err instanceof Error && err.name === "AbortError") {
|
|
733
|
-
throw
|
|
545
|
+
throw createTaggedError3(
|
|
734
546
|
"HTTP_TIMEOUT",
|
|
735
547
|
`Request to ${url} timed out after ${timeoutMs}ms`,
|
|
736
548
|
{ url, timeoutMs }
|
|
737
549
|
);
|
|
738
550
|
}
|
|
739
|
-
throw
|
|
551
|
+
throw createTaggedError3(
|
|
740
552
|
"UPSTREAM_ERROR",
|
|
741
553
|
`Fetch failed for ${url}: ${err instanceof Error ? err.message : String(err)}`,
|
|
742
554
|
{ url }
|
|
@@ -746,7 +558,7 @@ var fetchTextHandler = (async (args) => {
|
|
|
746
558
|
}
|
|
747
559
|
const contentLength = response.headers.get("content-length");
|
|
748
560
|
if (contentLength && parseInt(contentLength, 10) > maxBytes) {
|
|
749
|
-
throw
|
|
561
|
+
throw createTaggedError3(
|
|
750
562
|
"HTTP_TOO_LARGE",
|
|
751
563
|
`Response Content-Length ${contentLength} exceeds limit of ${maxBytes} bytes`,
|
|
752
564
|
{ url, contentLength: parseInt(contentLength, 10), limit: maxBytes }
|
|
@@ -791,7 +603,7 @@ async function readResponseWithLimit(response, maxBytes, url) {
|
|
|
791
603
|
totalBytes += value.byteLength;
|
|
792
604
|
if (totalBytes > maxBytes) {
|
|
793
605
|
reader.cancel();
|
|
794
|
-
throw
|
|
606
|
+
throw createTaggedError3(
|
|
795
607
|
"HTTP_TOO_LARGE",
|
|
796
608
|
`Response body exceeded limit of ${maxBytes} bytes while reading from ${url}`,
|
|
797
609
|
{ url, bytesRead: totalBytes, limit: maxBytes }
|
|
@@ -807,19 +619,24 @@ async function readResponseWithLimit(response, maxBytes, url) {
|
|
|
807
619
|
}
|
|
808
620
|
|
|
809
621
|
// http/fetchJson.ts
|
|
810
|
-
import {
|
|
622
|
+
import { validateUrl as validateUrl2 } from "@easynet/agent-tool";
|
|
623
|
+
import { createTaggedError as createTaggedError4 } from "@easynet/agent-tool";
|
|
811
624
|
var fetchJsonHandler = (async (args) => {
|
|
812
625
|
const ctx = getBuiltinContext();
|
|
813
626
|
const url = (args.url ?? args.uri)?.trim();
|
|
814
627
|
if (!url) {
|
|
815
|
-
throw
|
|
628
|
+
throw createTaggedError4("HTTP_INVALID", "url is required (pass 'url' or 'uri')", {});
|
|
816
629
|
}
|
|
817
630
|
const method = args.method ?? "GET";
|
|
818
631
|
const headers = args.headers ?? {};
|
|
819
632
|
const body = args.body ?? void 0;
|
|
820
633
|
const timeoutMs = args.timeoutMs ?? ctx.config.defaultTimeoutMs;
|
|
821
634
|
const maxBytes = args.maxBytes ?? ctx.config.maxHttpBytes;
|
|
822
|
-
await
|
|
635
|
+
await validateUrl2(url, {
|
|
636
|
+
allowedHosts: ctx.config.allowedHosts,
|
|
637
|
+
blockedHosts: ctx.config.blockedHosts,
|
|
638
|
+
blockedCidrs: ctx.config.blockedCidrs
|
|
639
|
+
});
|
|
823
640
|
if (!headers["Accept"] && !headers["accept"]) {
|
|
824
641
|
headers["Accept"] = "application/json";
|
|
825
642
|
}
|
|
@@ -838,13 +655,13 @@ var fetchJsonHandler = (async (args) => {
|
|
|
838
655
|
});
|
|
839
656
|
} catch (err) {
|
|
840
657
|
if (err instanceof Error && err.name === "AbortError") {
|
|
841
|
-
throw
|
|
658
|
+
throw createTaggedError4(
|
|
842
659
|
"HTTP_TIMEOUT",
|
|
843
660
|
`Request to ${url} timed out after ${timeoutMs}ms`,
|
|
844
661
|
{ url, timeoutMs }
|
|
845
662
|
);
|
|
846
663
|
}
|
|
847
|
-
throw
|
|
664
|
+
throw createTaggedError4(
|
|
848
665
|
"UPSTREAM_ERROR",
|
|
849
666
|
`Fetch failed for ${url}: ${err instanceof Error ? err.message : String(err)}`,
|
|
850
667
|
{ url }
|
|
@@ -854,7 +671,7 @@ var fetchJsonHandler = (async (args) => {
|
|
|
854
671
|
}
|
|
855
672
|
const contentLength = response.headers.get("content-length");
|
|
856
673
|
if (contentLength && parseInt(contentLength, 10) > maxBytes) {
|
|
857
|
-
throw
|
|
674
|
+
throw createTaggedError4(
|
|
858
675
|
"HTTP_TOO_LARGE",
|
|
859
676
|
`Response Content-Length ${contentLength} exceeds limit of ${maxBytes} bytes`,
|
|
860
677
|
{ url, contentLength: parseInt(contentLength, 10), limit: maxBytes }
|
|
@@ -863,7 +680,7 @@ var fetchJsonHandler = (async (args) => {
|
|
|
863
680
|
const text = await response.text();
|
|
864
681
|
const bytes = Buffer.byteLength(text, "utf-8");
|
|
865
682
|
if (bytes > maxBytes) {
|
|
866
|
-
throw
|
|
683
|
+
throw createTaggedError4(
|
|
867
684
|
"HTTP_TOO_LARGE",
|
|
868
685
|
`Response body ${bytes} bytes exceeds limit of ${maxBytes} bytes`,
|
|
869
686
|
{ url, bytes, limit: maxBytes }
|
|
@@ -873,7 +690,7 @@ var fetchJsonHandler = (async (args) => {
|
|
|
873
690
|
try {
|
|
874
691
|
json = JSON.parse(text);
|
|
875
692
|
} catch {
|
|
876
|
-
throw
|
|
693
|
+
throw createTaggedError4(
|
|
877
694
|
"UPSTREAM_ERROR",
|
|
878
695
|
`Failed to parse JSON response from ${url}: ${text.slice(0, 200)}`,
|
|
879
696
|
{ url, status: response.status, textPreview: text.slice(0, 500) }
|
|
@@ -900,28 +717,33 @@ var fetchJsonHandler = (async (args) => {
|
|
|
900
717
|
// http/downloadFile.ts
|
|
901
718
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
902
719
|
import { createHash as createHash3 } from "crypto";
|
|
903
|
-
import { dirname as
|
|
904
|
-
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";
|
|
905
723
|
var downloadFileHandler = (async (args) => {
|
|
906
724
|
const ctx = getBuiltinContext();
|
|
907
725
|
const url = (args.url ?? args.uri)?.trim();
|
|
908
726
|
if (!url) {
|
|
909
|
-
throw
|
|
727
|
+
throw createTaggedError5("HTTP_INVALID", "url is required (pass 'url' or 'uri')", {});
|
|
910
728
|
}
|
|
911
729
|
const destPath = (args.destPath ?? args.destination ?? args.filePath)?.trim();
|
|
912
730
|
if (!destPath) {
|
|
913
|
-
throw
|
|
731
|
+
throw createTaggedError5("HTTP_INVALID", "destPath is required (pass 'destPath', 'destination', or 'filePath')", {});
|
|
914
732
|
}
|
|
915
733
|
const headers = args.headers ?? {};
|
|
916
734
|
const timeoutMs = args.timeoutMs ?? ctx.config.defaultTimeoutMs;
|
|
917
735
|
const maxBytes = args.maxBytes ?? ctx.config.maxDownloadBytes;
|
|
918
736
|
const overwrite = args.overwrite ?? false;
|
|
919
|
-
await
|
|
920
|
-
|
|
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);
|
|
921
743
|
if (!overwrite) {
|
|
922
|
-
const { access
|
|
744
|
+
const { access } = await import("fs/promises");
|
|
923
745
|
try {
|
|
924
|
-
await
|
|
746
|
+
await access(resolvedDest);
|
|
925
747
|
throw new Error(
|
|
926
748
|
`File already exists: ${resolvedDest}. Set overwrite=true to allow overwriting.`
|
|
927
749
|
);
|
|
@@ -946,13 +768,13 @@ var downloadFileHandler = (async (args) => {
|
|
|
946
768
|
});
|
|
947
769
|
} catch (err) {
|
|
948
770
|
if (err instanceof Error && err.name === "AbortError") {
|
|
949
|
-
throw
|
|
771
|
+
throw createTaggedError5(
|
|
950
772
|
"HTTP_TIMEOUT",
|
|
951
773
|
`Download from ${url} timed out after ${timeoutMs}ms`,
|
|
952
774
|
{ url, timeoutMs }
|
|
953
775
|
);
|
|
954
776
|
}
|
|
955
|
-
throw
|
|
777
|
+
throw createTaggedError5(
|
|
956
778
|
"UPSTREAM_ERROR",
|
|
957
779
|
`Download failed for ${url}: ${err instanceof Error ? err.message : String(err)}`,
|
|
958
780
|
{ url }
|
|
@@ -962,14 +784,14 @@ var downloadFileHandler = (async (args) => {
|
|
|
962
784
|
}
|
|
963
785
|
const contentLength = response.headers.get("content-length");
|
|
964
786
|
if (contentLength && parseInt(contentLength, 10) > maxBytes) {
|
|
965
|
-
throw
|
|
787
|
+
throw createTaggedError5(
|
|
966
788
|
"HTTP_TOO_LARGE",
|
|
967
789
|
`Download Content-Length ${contentLength} exceeds limit of ${maxBytes} bytes`,
|
|
968
790
|
{ url, contentLength: parseInt(contentLength, 10), limit: maxBytes }
|
|
969
791
|
);
|
|
970
792
|
}
|
|
971
793
|
if (!response.body) {
|
|
972
|
-
throw
|
|
794
|
+
throw createTaggedError5("UPSTREAM_ERROR", `No response body from ${url}`, { url });
|
|
973
795
|
}
|
|
974
796
|
const reader = response.body.getReader();
|
|
975
797
|
const chunks = [];
|
|
@@ -982,7 +804,7 @@ var downloadFileHandler = (async (args) => {
|
|
|
982
804
|
totalBytes += value.byteLength;
|
|
983
805
|
if (totalBytes > maxBytes) {
|
|
984
806
|
reader.cancel();
|
|
985
|
-
throw
|
|
807
|
+
throw createTaggedError5(
|
|
986
808
|
"HTTP_TOO_LARGE",
|
|
987
809
|
`Download from ${url} exceeded limit of ${maxBytes} bytes (received ${totalBytes})`,
|
|
988
810
|
{ url, bytesRead: totalBytes, limit: maxBytes }
|
|
@@ -995,7 +817,7 @@ var downloadFileHandler = (async (args) => {
|
|
|
995
817
|
reader.releaseLock();
|
|
996
818
|
}
|
|
997
819
|
const sha256 = hasher.digest("hex");
|
|
998
|
-
await mkdir2(
|
|
820
|
+
await mkdir2(dirname2(resolvedDest), { recursive: true });
|
|
999
821
|
const buffer = Buffer.concat(chunks);
|
|
1000
822
|
await writeFile2(resolvedDest, buffer);
|
|
1001
823
|
return {
|
|
@@ -1024,16 +846,21 @@ var downloadFileHandler = (async (args) => {
|
|
|
1024
846
|
});
|
|
1025
847
|
|
|
1026
848
|
// http/head.ts
|
|
1027
|
-
import {
|
|
849
|
+
import { validateUrl as validateUrl4 } from "@easynet/agent-tool";
|
|
850
|
+
import { createTaggedError as createTaggedError6 } from "@easynet/agent-tool";
|
|
1028
851
|
var headHandler = (async (args) => {
|
|
1029
852
|
const ctx = getBuiltinContext();
|
|
1030
853
|
const url = (args.url ?? args.uri)?.trim();
|
|
1031
854
|
if (!url) {
|
|
1032
|
-
throw
|
|
855
|
+
throw createTaggedError6("HTTP_INVALID", "url is required (pass 'url' or 'uri')", {});
|
|
1033
856
|
}
|
|
1034
857
|
const headers = args.headers ?? {};
|
|
1035
858
|
const timeoutMs = args.timeoutMs ?? ctx.config.defaultTimeoutMs;
|
|
1036
|
-
await
|
|
859
|
+
await validateUrl4(url, {
|
|
860
|
+
allowedHosts: ctx.config.allowedHosts,
|
|
861
|
+
blockedHosts: ctx.config.blockedHosts,
|
|
862
|
+
blockedCidrs: ctx.config.blockedCidrs
|
|
863
|
+
});
|
|
1037
864
|
if (!headers["User-Agent"] && !headers["user-agent"]) {
|
|
1038
865
|
headers["User-Agent"] = ctx.config.httpUserAgent;
|
|
1039
866
|
}
|
|
@@ -1048,13 +875,13 @@ var headHandler = (async (args) => {
|
|
|
1048
875
|
});
|
|
1049
876
|
} catch (err) {
|
|
1050
877
|
if (err instanceof Error && err.name === "AbortError") {
|
|
1051
|
-
throw
|
|
878
|
+
throw createTaggedError6(
|
|
1052
879
|
"HTTP_TIMEOUT",
|
|
1053
880
|
`HEAD request to ${url} timed out after ${timeoutMs}ms`,
|
|
1054
881
|
{ url, timeoutMs }
|
|
1055
882
|
);
|
|
1056
883
|
}
|
|
1057
|
-
throw
|
|
884
|
+
throw createTaggedError6(
|
|
1058
885
|
"UPSTREAM_ERROR",
|
|
1059
886
|
`HEAD request failed for ${url}: ${err instanceof Error ? err.message : String(err)}`,
|
|
1060
887
|
{ url }
|
|
@@ -1084,18 +911,23 @@ var headHandler = (async (args) => {
|
|
|
1084
911
|
});
|
|
1085
912
|
|
|
1086
913
|
// http/duckduckgoSearch.ts
|
|
1087
|
-
import {
|
|
914
|
+
import { validateUrl as validateUrl5 } from "@easynet/agent-tool";
|
|
915
|
+
import { createTaggedError as createTaggedError7 } from "@easynet/agent-tool";
|
|
1088
916
|
var DUCKDUCKGO_API = "https://api.duckduckgo.com/";
|
|
1089
917
|
var duckduckgoSearchHandler = (async (args) => {
|
|
1090
918
|
const ctx = getBuiltinContext();
|
|
1091
919
|
const query = (args.query ?? args.q)?.trim();
|
|
1092
920
|
if (!query) {
|
|
1093
|
-
throw
|
|
921
|
+
throw createTaggedError7("DUCKDUCKGO_INVALID", "query is required (pass 'query' or 'q')", {});
|
|
1094
922
|
}
|
|
1095
923
|
const timeoutMs = args.timeoutMs ?? ctx.config.defaultTimeoutMs;
|
|
1096
924
|
const maxResults = args.maxResults ?? 10;
|
|
1097
925
|
const url = `${DUCKDUCKGO_API}?q=${encodeURIComponent(query)}&format=json`;
|
|
1098
|
-
await
|
|
926
|
+
await validateUrl5(url, {
|
|
927
|
+
allowedHosts: ctx.config.allowedHosts,
|
|
928
|
+
blockedHosts: ctx.config.blockedHosts,
|
|
929
|
+
blockedCidrs: ctx.config.blockedCidrs
|
|
930
|
+
});
|
|
1099
931
|
const controller = new AbortController();
|
|
1100
932
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1101
933
|
let response;
|
|
@@ -1108,13 +940,13 @@ var duckduckgoSearchHandler = (async (args) => {
|
|
|
1108
940
|
} catch (err) {
|
|
1109
941
|
clearTimeout(timer);
|
|
1110
942
|
if (err instanceof Error && err.name === "AbortError") {
|
|
1111
|
-
throw
|
|
943
|
+
throw createTaggedError7(
|
|
1112
944
|
"HTTP_TIMEOUT",
|
|
1113
945
|
`DuckDuckGo search timed out after ${timeoutMs}ms`,
|
|
1114
946
|
{ query, timeoutMs }
|
|
1115
947
|
);
|
|
1116
948
|
}
|
|
1117
|
-
throw
|
|
949
|
+
throw createTaggedError7(
|
|
1118
950
|
"UPSTREAM_ERROR",
|
|
1119
951
|
`DuckDuckGo search failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1120
952
|
{ query }
|
|
@@ -1125,7 +957,7 @@ var duckduckgoSearchHandler = (async (args) => {
|
|
|
1125
957
|
const text = await response.text();
|
|
1126
958
|
const bytes = Buffer.byteLength(text, "utf-8");
|
|
1127
959
|
if (bytes > maxBytes) {
|
|
1128
|
-
throw
|
|
960
|
+
throw createTaggedError7(
|
|
1129
961
|
"HTTP_TOO_LARGE",
|
|
1130
962
|
`DuckDuckGo response ${bytes} bytes exceeds limit of ${maxBytes} bytes`,
|
|
1131
963
|
{ query, bytes, limit: maxBytes }
|
|
@@ -1135,7 +967,7 @@ var duckduckgoSearchHandler = (async (args) => {
|
|
|
1135
967
|
try {
|
|
1136
968
|
raw = JSON.parse(text);
|
|
1137
969
|
} catch {
|
|
1138
|
-
throw
|
|
970
|
+
throw createTaggedError7(
|
|
1139
971
|
"UPSTREAM_ERROR",
|
|
1140
972
|
`DuckDuckGo returned invalid JSON`,
|
|
1141
973
|
{ query, textPreview: text.slice(0, 200) }
|
|
@@ -1186,7 +1018,8 @@ var duckduckgoSearchHandler = (async (args) => {
|
|
|
1186
1018
|
|
|
1187
1019
|
// http/fetchPageMainContent.ts
|
|
1188
1020
|
import { parse } from "node-html-parser";
|
|
1189
|
-
import {
|
|
1021
|
+
import { validateUrl as validateUrl6 } from "@easynet/agent-tool";
|
|
1022
|
+
import { createTaggedError as createTaggedError8 } from "@easynet/agent-tool";
|
|
1190
1023
|
var MAIN_SELECTORS = [
|
|
1191
1024
|
"main",
|
|
1192
1025
|
"article",
|
|
@@ -1229,11 +1062,15 @@ var fetchPageMainContentHandler = (async (args) => {
|
|
|
1229
1062
|
const ctx = getBuiltinContext();
|
|
1230
1063
|
const url = (args.url ?? args.uri)?.trim();
|
|
1231
1064
|
if (!url) {
|
|
1232
|
-
throw
|
|
1065
|
+
throw createTaggedError8("HTTP_INVALID", "url is required (pass 'url' or 'uri')", {});
|
|
1233
1066
|
}
|
|
1234
1067
|
const timeoutMs = args.timeoutMs ?? ctx.config.defaultTimeoutMs;
|
|
1235
1068
|
const maxBytes = args.maxBytes ?? ctx.config.maxHttpBytes;
|
|
1236
|
-
await
|
|
1069
|
+
await validateUrl6(url, {
|
|
1070
|
+
allowedHosts: ctx.config.allowedHosts,
|
|
1071
|
+
blockedHosts: ctx.config.blockedHosts,
|
|
1072
|
+
blockedCidrs: ctx.config.blockedCidrs
|
|
1073
|
+
});
|
|
1237
1074
|
const controller = new AbortController();
|
|
1238
1075
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1239
1076
|
let response;
|
|
@@ -1246,13 +1083,13 @@ var fetchPageMainContentHandler = (async (args) => {
|
|
|
1246
1083
|
} catch (err) {
|
|
1247
1084
|
clearTimeout(timer);
|
|
1248
1085
|
if (err instanceof Error && err.name === "AbortError") {
|
|
1249
|
-
throw
|
|
1086
|
+
throw createTaggedError8(
|
|
1250
1087
|
"HTTP_TIMEOUT",
|
|
1251
1088
|
`Request to ${url} timed out after ${timeoutMs}ms`,
|
|
1252
1089
|
{ url, timeoutMs }
|
|
1253
1090
|
);
|
|
1254
1091
|
}
|
|
1255
|
-
throw
|
|
1092
|
+
throw createTaggedError8(
|
|
1256
1093
|
"UPSTREAM_ERROR",
|
|
1257
1094
|
`Fetch failed for ${url}: ${err instanceof Error ? err.message : String(err)}`,
|
|
1258
1095
|
{ url }
|
|
@@ -1261,7 +1098,7 @@ var fetchPageMainContentHandler = (async (args) => {
|
|
|
1261
1098
|
clearTimeout(timer);
|
|
1262
1099
|
const contentLength = response.headers.get("content-length");
|
|
1263
1100
|
if (contentLength && parseInt(contentLength, 10) > maxBytes) {
|
|
1264
|
-
throw
|
|
1101
|
+
throw createTaggedError8(
|
|
1265
1102
|
"HTTP_TOO_LARGE",
|
|
1266
1103
|
`Response Content-Length ${contentLength} exceeds limit of ${maxBytes} bytes`,
|
|
1267
1104
|
{ url, contentLength: parseInt(contentLength, 10), limit: maxBytes }
|
|
@@ -1270,7 +1107,7 @@ var fetchPageMainContentHandler = (async (args) => {
|
|
|
1270
1107
|
const rawText = await response.text();
|
|
1271
1108
|
const rawBytes = Buffer.byteLength(rawText, "utf-8");
|
|
1272
1109
|
if (rawBytes > maxBytes) {
|
|
1273
|
-
throw
|
|
1110
|
+
throw createTaggedError8(
|
|
1274
1111
|
"HTTP_TOO_LARGE",
|
|
1275
1112
|
`Response body ${rawBytes} bytes exceeds limit of ${maxBytes} bytes`,
|
|
1276
1113
|
{ url, bytes: rawBytes, limit: maxBytes }
|
|
@@ -1313,14 +1150,15 @@ var fetchPageMainContentHandler = (async (args) => {
|
|
|
1313
1150
|
});
|
|
1314
1151
|
|
|
1315
1152
|
// http/yahooFinance.ts
|
|
1316
|
-
import {
|
|
1153
|
+
import { validateUrl as validateUrl7 } from "@easynet/agent-tool";
|
|
1154
|
+
import { createTaggedError as createTaggedError9 } from "@easynet/agent-tool";
|
|
1317
1155
|
var YAHOO_CHART_BASE = "https://query1.finance.yahoo.com/v8/finance/chart/";
|
|
1318
1156
|
var yahooFinanceQuoteHandler = (async (args) => {
|
|
1319
1157
|
const ctx = getBuiltinContext();
|
|
1320
1158
|
const rawSymbols = args.symbols ?? args.symbol ?? args.tickers;
|
|
1321
1159
|
const symbols = Array.isArray(rawSymbols) ? rawSymbols.map((s) => String(s).trim().toUpperCase()).filter(Boolean) : rawSymbols != null ? [String(rawSymbols).trim().toUpperCase()].filter(Boolean) : [];
|
|
1322
1160
|
if (symbols.length === 0) {
|
|
1323
|
-
throw
|
|
1161
|
+
throw createTaggedError9(
|
|
1324
1162
|
"YAHOO_INVALID",
|
|
1325
1163
|
"At least one symbol is required",
|
|
1326
1164
|
{}
|
|
@@ -1332,7 +1170,11 @@ var yahooFinanceQuoteHandler = (async (args) => {
|
|
|
1332
1170
|
const quotes = [];
|
|
1333
1171
|
for (const symbol of symbols) {
|
|
1334
1172
|
const url2 = `${YAHOO_CHART_BASE}${encodeURIComponent(symbol)}?interval=1d&range=${range}`;
|
|
1335
|
-
await
|
|
1173
|
+
await validateUrl7(url2, {
|
|
1174
|
+
allowedHosts: ctx.config.allowedHosts,
|
|
1175
|
+
blockedHosts: ctx.config.blockedHosts,
|
|
1176
|
+
blockedCidrs: ctx.config.blockedCidrs
|
|
1177
|
+
});
|
|
1336
1178
|
const controller = new AbortController();
|
|
1337
1179
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1338
1180
|
let response;
|
|
@@ -1348,13 +1190,13 @@ var yahooFinanceQuoteHandler = (async (args) => {
|
|
|
1348
1190
|
} catch (err) {
|
|
1349
1191
|
clearTimeout(timer);
|
|
1350
1192
|
if (err instanceof Error && err.name === "AbortError") {
|
|
1351
|
-
throw
|
|
1193
|
+
throw createTaggedError9(
|
|
1352
1194
|
"HTTP_TIMEOUT",
|
|
1353
1195
|
`Yahoo Finance request for ${symbol} timed out after ${timeoutMs}ms`,
|
|
1354
1196
|
{ symbol, timeoutMs }
|
|
1355
1197
|
);
|
|
1356
1198
|
}
|
|
1357
|
-
throw
|
|
1199
|
+
throw createTaggedError9(
|
|
1358
1200
|
"UPSTREAM_ERROR",
|
|
1359
1201
|
`Yahoo Finance request failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1360
1202
|
{ symbol }
|
|
@@ -1364,7 +1206,7 @@ var yahooFinanceQuoteHandler = (async (args) => {
|
|
|
1364
1206
|
const text = await response.text();
|
|
1365
1207
|
const bytes = Buffer.byteLength(text, "utf-8");
|
|
1366
1208
|
if (bytes > maxBytes) {
|
|
1367
|
-
throw
|
|
1209
|
+
throw createTaggedError9(
|
|
1368
1210
|
"HTTP_TOO_LARGE",
|
|
1369
1211
|
`Yahoo Finance response ${bytes} bytes exceeds limit of ${maxBytes} bytes`,
|
|
1370
1212
|
{ symbol, bytes, limit: maxBytes }
|
|
@@ -1374,7 +1216,7 @@ var yahooFinanceQuoteHandler = (async (args) => {
|
|
|
1374
1216
|
try {
|
|
1375
1217
|
data = JSON.parse(text);
|
|
1376
1218
|
} catch {
|
|
1377
|
-
throw
|
|
1219
|
+
throw createTaggedError9(
|
|
1378
1220
|
"UPSTREAM_ERROR",
|
|
1379
1221
|
"Yahoo Finance returned invalid JSON",
|
|
1380
1222
|
{ symbol, textPreview: text.slice(0, 200) }
|
|
@@ -1382,7 +1224,7 @@ var yahooFinanceQuoteHandler = (async (args) => {
|
|
|
1382
1224
|
}
|
|
1383
1225
|
const error = data.chart?.error;
|
|
1384
1226
|
if (error?.description) {
|
|
1385
|
-
throw
|
|
1227
|
+
throw createTaggedError9(
|
|
1386
1228
|
"YAHOO_ERROR",
|
|
1387
1229
|
`Yahoo Finance error for ${symbol}: ${error.description}`,
|
|
1388
1230
|
{ symbol, code: error.code }
|
|
@@ -1595,12 +1437,13 @@ var templateRenderHandler = (async (args) => {
|
|
|
1595
1437
|
// exec/runCommand.ts
|
|
1596
1438
|
import { spawn } from "child_process";
|
|
1597
1439
|
import { resolve as pathResolve } from "path";
|
|
1598
|
-
import {
|
|
1440
|
+
import { resolveSandboxedPath as resolveSandboxedPath8 } from "@easynet/agent-tool";
|
|
1441
|
+
import { createTaggedError as createTaggedError10 } from "@easynet/agent-tool";
|
|
1599
1442
|
var runCommandHandler = (async (args) => {
|
|
1600
1443
|
const ctx = getBuiltinContext();
|
|
1601
1444
|
const { allowedCommands, maxCommandOutputBytes, commandTimeoutMs } = ctx.config;
|
|
1602
1445
|
if (!allowedCommands.length) {
|
|
1603
|
-
throw
|
|
1446
|
+
throw createTaggedError10(
|
|
1604
1447
|
"EXEC_DISABLED",
|
|
1605
1448
|
"Exec is disabled: allowedCommands is empty",
|
|
1606
1449
|
{}
|
|
@@ -1608,18 +1451,18 @@ var runCommandHandler = (async (args) => {
|
|
|
1608
1451
|
}
|
|
1609
1452
|
const rawCommand = (args.command ?? args.cmd)?.trim();
|
|
1610
1453
|
if (!rawCommand) {
|
|
1611
|
-
throw
|
|
1454
|
+
throw createTaggedError10("EXEC_INVALID", "command is required (pass 'command' or 'cmd')", {});
|
|
1612
1455
|
}
|
|
1613
1456
|
const baseName = rawCommand.replace(/^.*\//, "").trim();
|
|
1614
1457
|
if (baseName !== rawCommand || /[;&|$`\s]/.test(rawCommand)) {
|
|
1615
|
-
throw
|
|
1458
|
+
throw createTaggedError10(
|
|
1616
1459
|
"EXEC_INVALID",
|
|
1617
1460
|
"command must be a single executable name (no path, no shell chars)",
|
|
1618
1461
|
{ command: rawCommand }
|
|
1619
1462
|
);
|
|
1620
1463
|
}
|
|
1621
1464
|
if (!allowedCommands.includes(baseName)) {
|
|
1622
|
-
throw
|
|
1465
|
+
throw createTaggedError10(
|
|
1623
1466
|
"EXEC_NOT_ALLOWED",
|
|
1624
1467
|
`Command "${baseName}" is not in allowedCommands`,
|
|
1625
1468
|
{ command: baseName, allowed: allowedCommands }
|
|
@@ -1629,7 +1472,7 @@ var runCommandHandler = (async (args) => {
|
|
|
1629
1472
|
const timeoutMs = args.timeoutMs ?? commandTimeoutMs;
|
|
1630
1473
|
let cwd = pathResolve(ctx.config.sandboxRoot);
|
|
1631
1474
|
if (args.cwd != null && args.cwd !== "") {
|
|
1632
|
-
cwd = await
|
|
1475
|
+
cwd = await resolveSandboxedPath8(args.cwd, ctx.config.sandboxRoot);
|
|
1633
1476
|
}
|
|
1634
1477
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
1635
1478
|
const proc = spawn(baseName, cmdArgs, {
|
|
@@ -1646,7 +1489,7 @@ var runCommandHandler = (async (args) => {
|
|
|
1646
1489
|
if (totalBytes + len > maxCommandOutputBytes) {
|
|
1647
1490
|
proc.kill("SIGKILL");
|
|
1648
1491
|
rejectPromise(
|
|
1649
|
-
|
|
1492
|
+
createTaggedError10(
|
|
1650
1493
|
"EXEC_OUTPUT_TOO_LARGE",
|
|
1651
1494
|
`Command output exceeded ${maxCommandOutputBytes} bytes`,
|
|
1652
1495
|
{ maxBytes: maxCommandOutputBytes }
|
|
@@ -1684,7 +1527,7 @@ var runCommandHandler = (async (args) => {
|
|
|
1684
1527
|
proc.on("error", (err) => {
|
|
1685
1528
|
clearTimeout(timeout);
|
|
1686
1529
|
rejectPromise(
|
|
1687
|
-
|
|
1530
|
+
createTaggedError10("EXEC_SPAWN_ERROR", err.message, { command: baseName })
|
|
1688
1531
|
);
|
|
1689
1532
|
});
|
|
1690
1533
|
proc.on("close", (code, signal) => {
|
|
@@ -1918,7 +1761,8 @@ var CORE_GROUP_PREFIX = {
|
|
|
1918
1761
|
function registerCoreTools(registry, userConfig, options) {
|
|
1919
1762
|
const config = {
|
|
1920
1763
|
...DEFAULT_CORE_TOOLS_CONFIG,
|
|
1921
|
-
...userConfig
|
|
1764
|
+
...userConfig,
|
|
1765
|
+
blockedHosts: userConfig.blockedHosts ?? []
|
|
1922
1766
|
};
|
|
1923
1767
|
const adapter = new CoreAdapter(config);
|
|
1924
1768
|
const onlySet = options?.only?.length ? new Set(options.only) : null;
|
|
@@ -1935,6 +1779,9 @@ function registerCoreTools(registry, userConfig, options) {
|
|
|
1935
1779
|
}
|
|
1936
1780
|
return adapter;
|
|
1937
1781
|
}
|
|
1782
|
+
|
|
1783
|
+
// index.ts
|
|
1784
|
+
import { resolveSandboxedPath as resolveSandboxedPath9, validateUrl as validateUrl8, isIpInBlockedCidrs } from "@easynet/agent-tool";
|
|
1938
1785
|
export {
|
|
1939
1786
|
CoreAdapter,
|
|
1940
1787
|
DEFAULT_CORE_TOOLS_CONFIG,
|
|
@@ -1954,14 +1801,14 @@ export {
|
|
|
1954
1801
|
nowSpec,
|
|
1955
1802
|
readTextSpec,
|
|
1956
1803
|
registerCoreTools,
|
|
1957
|
-
resolveSandboxedPath,
|
|
1804
|
+
resolveSandboxedPath9 as resolveSandboxedPath,
|
|
1958
1805
|
runCommandSpec,
|
|
1959
1806
|
runWithBuiltinContext,
|
|
1960
1807
|
searchTextSpec,
|
|
1961
1808
|
sha256Spec,
|
|
1962
1809
|
templateRenderSpec,
|
|
1963
1810
|
truncateSpec,
|
|
1964
|
-
validateUrl,
|
|
1811
|
+
validateUrl8 as validateUrl,
|
|
1965
1812
|
writeTextSpec,
|
|
1966
1813
|
yahooFinanceQuoteSpec
|
|
1967
1814
|
};
|