@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/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 createTaggedError2("FS_INVALID", "path is required (pass 'path' or 'filePath')", {});
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 createTaggedError2(
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 as dirname2 } from "path";
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 resolveSandboxedPath(inputPath, ctx.config.sandboxRoot);
156
+ const resolvedPath = await resolveSandboxedPath2(inputPath, ctx.config.sandboxRoot);
199
157
  if (!overwrite) {
200
- const { access: access2 } = await import("fs/promises");
158
+ const { access } = await import("fs/promises");
201
159
  try {
202
- await access2(resolvedPath);
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(dirname2(resolvedPath), { recursive: true });
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 as resolve2, join } from "path";
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 resolveSandboxedPath(inputPath, ctx.config.sandboxRoot);
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 ? resolve2(basePath, relativePath) : basePath;
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 resolveSandboxedPath(rootPath, ctx.config.sandboxRoot);
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 { createTaggedError as createTaggedError3 } from "@easynet/agent-tool";
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 createTaggedError3("FS_INVALID", "path is required (pass 'path' or 'filePath')", {});
428
+ throw createTaggedError2("FS_INVALID", "path is required (pass 'path' or 'filePath')", {});
468
429
  }
469
- const resolvedPath = await resolveSandboxedPath(inputPath, ctx.config.sandboxRoot);
430
+ const resolvedPath = await resolveSandboxedPath5(inputPath, ctx.config.sandboxRoot);
470
431
  const fileStat = await stat4(resolvedPath);
471
- const hash = await new Promise((resolve3, reject) => {
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", () => resolve3(hasher.digest("hex")));
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 resolveSandboxedPath(inputPath, ctx.config.sandboxRoot);
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 { createTaggedError as createTaggedError5 } from "@easynet/agent-tool";
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 createTaggedError5("HTTP_INVALID", "url is required (pass 'url' or 'uri')", {});
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, ctx.config.allowedHosts, ctx.config.blockedCidrs);
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 createTaggedError5(
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 createTaggedError5(
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 createTaggedError5(
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 createTaggedError5(
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 { createTaggedError as createTaggedError6 } from "@easynet/agent-tool";
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 createTaggedError6("HTTP_INVALID", "url is required (pass 'url' or 'uri')", {});
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 validateUrl(url, ctx.config.allowedHosts, ctx.config.blockedCidrs);
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 createTaggedError6(
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 createTaggedError6(
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 createTaggedError6(
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 createTaggedError6(
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 createTaggedError6(
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 dirname3 } from "path";
904
- import { createTaggedError as createTaggedError7 } from "@easynet/agent-tool";
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 createTaggedError7("HTTP_INVALID", "url is required (pass 'url' or 'uri')", {});
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 createTaggedError7("HTTP_INVALID", "destPath is required (pass 'destPath', 'destination', or 'filePath')", {});
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 validateUrl(url, ctx.config.allowedHosts, ctx.config.blockedCidrs);
920
- const resolvedDest = await resolveSandboxedPath(destPath, ctx.config.sandboxRoot);
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: access2 } = await import("fs/promises");
744
+ const { access } = await import("fs/promises");
923
745
  try {
924
- await access2(resolvedDest);
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 createTaggedError7(
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 createTaggedError7(
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 createTaggedError7(
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 createTaggedError7("UPSTREAM_ERROR", `No response body from ${url}`, { url });
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 createTaggedError7(
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(dirname3(resolvedDest), { recursive: true });
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 { createTaggedError as createTaggedError8 } from "@easynet/agent-tool";
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 createTaggedError8("HTTP_INVALID", "url is required (pass 'url' or 'uri')", {});
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 validateUrl(url, ctx.config.allowedHosts, ctx.config.blockedCidrs);
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 createTaggedError8(
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 createTaggedError8(
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 { createTaggedError as createTaggedError9 } from "@easynet/agent-tool";
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 createTaggedError9("DUCKDUCKGO_INVALID", "query is required (pass 'query' or 'q')", {});
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 validateUrl(url, ctx.config.allowedHosts, ctx.config.blockedCidrs);
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 createTaggedError9(
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 createTaggedError9(
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 createTaggedError9(
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 createTaggedError9(
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 { createTaggedError as createTaggedError10 } from "@easynet/agent-tool";
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 createTaggedError10("HTTP_INVALID", "url is required (pass 'url' or 'uri')", {});
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 validateUrl(url, ctx.config.allowedHosts, ctx.config.blockedCidrs);
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 createTaggedError10(
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 createTaggedError10(
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 createTaggedError10(
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 createTaggedError10(
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 { createTaggedError as createTaggedError11 } from "@easynet/agent-tool";
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 createTaggedError11(
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 validateUrl(url2, ctx.config.allowedHosts, ctx.config.blockedCidrs);
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 createTaggedError11(
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 createTaggedError11(
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 createTaggedError11(
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 createTaggedError11(
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 createTaggedError11(
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 { createTaggedError as createTaggedError12 } from "@easynet/agent-tool";
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 createTaggedError12(
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 createTaggedError12("EXEC_INVALID", "command is required (pass 'command' or 'cmd')", {});
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 createTaggedError12(
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 createTaggedError12(
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 resolveSandboxedPath(args.cwd, ctx.config.sandboxRoot);
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
- createTaggedError12(
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
- createTaggedError12("EXEC_SPAWN_ERROR", err.message, { command: baseName })
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
  };