@cryer/star-cli 0.3.13 → 0.3.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +17 -5
  2. package/README.zh-CN.md +15 -5
  3. package/dist/{chunk-6PLZ3FLG.js → chunk-2ISBRSBA.js} +375 -235
  4. package/dist/chunk-2ISBRSBA.js.map +1 -0
  5. package/dist/{chunk-QELCMXQX.js → chunk-4HANB3DJ.js} +381 -212
  6. package/dist/chunk-4HANB3DJ.js.map +1 -0
  7. package/dist/{chunk-OF42OVM4.js → chunk-54KH4JSO.js} +204 -125
  8. package/dist/chunk-54KH4JSO.js.map +1 -0
  9. package/dist/{chunk-RD4S5LKM.js → chunk-CECMCDZG.js} +43 -30
  10. package/dist/chunk-CECMCDZG.js.map +1 -0
  11. package/dist/{chunk-MVAMPVTA.js → chunk-KILMI3S5.js} +13 -5
  12. package/dist/{chunk-MVAMPVTA.js.map → chunk-KILMI3S5.js.map} +1 -1
  13. package/dist/{chunk-NMB3TNQU.js → chunk-NRLGGA7L.js} +507 -175
  14. package/dist/chunk-NRLGGA7L.js.map +1 -0
  15. package/dist/loop-M64D5YII.js +17 -0
  16. package/dist/main.js +177 -131
  17. package/dist/main.js.map +1 -1
  18. package/dist/{provider-EWMUPBLH.js → provider-3Z4B7RMF.js} +2 -2
  19. package/dist/{repl-ZABBT33D.js → repl-N2H7JDSH.js} +617 -327
  20. package/dist/repl-N2H7JDSH.js.map +1 -0
  21. package/dist/{tools-4OLADVDR.js → tools-5RKM7ZI3.js} +3 -3
  22. package/package.json +1 -1
  23. package/dist/chunk-6PLZ3FLG.js.map +0 -1
  24. package/dist/chunk-NMB3TNQU.js.map +0 -1
  25. package/dist/chunk-OF42OVM4.js.map +0 -1
  26. package/dist/chunk-QELCMXQX.js.map +0 -1
  27. package/dist/chunk-RD4S5LKM.js.map +0 -1
  28. package/dist/loop-DWQ5WTPD.js +0 -17
  29. package/dist/repl-ZABBT33D.js.map +0 -1
  30. /package/dist/{loop-DWQ5WTPD.js.map → loop-M64D5YII.js.map} +0 -0
  31. /package/dist/{provider-EWMUPBLH.js.map → provider-3Z4B7RMF.js.map} +0 -0
  32. /package/dist/{tools-4OLADVDR.js.map → tools-5RKM7ZI3.js.map} +0 -0
@@ -23,7 +23,7 @@ import {
23
23
  starHome,
24
24
  walkFiles,
25
25
  webFetchTool
26
- } from "./chunk-6PLZ3FLG.js";
26
+ } from "./chunk-2ISBRSBA.js";
27
27
 
28
28
  // src/tools/fs/edit.ts
29
29
  import { readFile, stat, writeFile } from "fs/promises";
@@ -42,6 +42,31 @@ function countOccurrences(haystack, needle) {
42
42
  function normalizeEol(text) {
43
43
  return text.replace(/\r\n/g, "\n");
44
44
  }
45
+ function normalizedOffsets(text) {
46
+ const offsets = [0];
47
+ for (let i = 0; i < text.length; i++) {
48
+ if (text[i] === "\r" && text[i + 1] === "\n") i++;
49
+ offsets.push(i + 1);
50
+ }
51
+ return offsets;
52
+ }
53
+ function replacePreservingEol(raw, normalized, offsets, oldString, newString, replaceAll, useCrlf) {
54
+ const replacement = useCrlf ? newString.replace(/\n/g, "\r\n") : newString;
55
+ let normalizedFrom = 0;
56
+ let rawFrom = 0;
57
+ let updated = "";
58
+ while (true) {
59
+ const match = normalized.indexOf(oldString, normalizedFrom);
60
+ if (match < 0) break;
61
+ const rawStart = offsets[match] ?? raw.length;
62
+ const rawEnd = offsets[match + oldString.length] ?? raw.length;
63
+ updated += raw.slice(rawFrom, rawStart) + replacement;
64
+ rawFrom = rawEnd;
65
+ normalizedFrom = match + oldString.length;
66
+ if (!replaceAll) break;
67
+ }
68
+ return updated + raw.slice(rawFrom);
69
+ }
45
70
  var schema = z.object({
46
71
  path: z.string().describe("File path, absolute or relative to the working directory"),
47
72
  old_string: z.string().min(1).describe("Exact text to replace"),
@@ -81,7 +106,8 @@ var editFileTool = {
81
106
  let working = content;
82
107
  let oldString = args.old_string;
83
108
  let newString = args.new_string;
84
- let restoreEol = null;
109
+ let offsets = null;
110
+ let useCrlf = false;
85
111
  let count = countOccurrences(working, oldString);
86
112
  if (count === 0) {
87
113
  const normalizedContent = normalizeEol(content);
@@ -90,7 +116,8 @@ var editFileTool = {
90
116
  if (normalizedCount > 0) {
91
117
  const crlfCount = countOccurrences(content, "\r\n");
92
118
  const loneLfCount = countOccurrences(content.replace(/\r\n/g, ""), "\n");
93
- restoreEol = crlfCount > 0 && crlfCount >= loneLfCount ? (text) => text.replace(/\n/g, "\r\n") : null;
119
+ useCrlf = crlfCount > 0 && crlfCount >= loneLfCount;
120
+ offsets = normalizedOffsets(content);
94
121
  working = normalizedContent;
95
122
  oldString = normalizedOld;
96
123
  newString = normalizeEol(newString);
@@ -106,10 +133,15 @@ var editFileTool = {
106
133
  isError: true
107
134
  };
108
135
  }
109
- let updated = args.replace_all ? working.split(oldString).join(newString) : working.replace(oldString, () => newString);
110
- if (restoreEol) {
111
- updated = restoreEol(updated);
112
- }
136
+ const updated = offsets ? replacePreservingEol(
137
+ content,
138
+ working,
139
+ offsets,
140
+ oldString,
141
+ newString,
142
+ args.replace_all === true,
143
+ useCrlf
144
+ ) : args.replace_all ? working.split(oldString).join(newString) : working.replace(oldString, () => newString);
113
145
  try {
114
146
  const now = await stat(filePath).catch(() => null);
115
147
  if (!now || now.mtimeMs !== readMtimeMs) {
@@ -180,13 +212,17 @@ var globTool = {
180
212
  const prefixSegments = literalDirPrefix(args.pattern);
181
213
  const walkRoot = prefixSegments.length > 0 ? path2.join(root, ...prefixSegments) : root;
182
214
  const prefix = prefixSegments.length > 0 ? `${prefixSegments.join("/")}/` : "";
183
- const { files, aborted } = await walkFiles(walkRoot, void 0, {
215
+ let matched = [];
216
+ const { aborted } = await walkFiles(walkRoot, void 0, {
184
217
  ignore: createIgnorePredicate(ctx.cwd),
185
218
  signal: ctx.abortSignal,
186
219
  symlinkBoundary: ctx.cwd,
187
- nestedIgnore: true
220
+ nestedIgnore: true,
221
+ collect: false,
222
+ onBatch: (files) => {
223
+ matched = matched.concat(files.filter((f) => matchesGlob(args.pattern, `${prefix}${f.rel}`))).sort((a, b) => b.mtimeMs - a.mtimeMs || (a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0)).slice(0, MAX_RESULTS);
224
+ }
188
225
  });
189
- const matched = files.filter((f) => matchesGlob(args.pattern, `${prefix}${f.rel}`)).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, MAX_RESULTS);
190
226
  if (matched.length === 0) {
191
227
  return {
192
228
  content: aborted ? `Search interrupted before any files matched pattern: ${args.pattern}` : `No files matched pattern: ${args.pattern}`
@@ -236,44 +272,13 @@ var grepTool = {
236
272
  return { content: `Invalid regular expression: ${err.message}`, isError: true };
237
273
  }
238
274
  const root = path3.resolve(ctx.cwd, args.path ?? ".");
239
- let files;
240
275
  let interrupted = false;
241
- try {
242
- const st = await stat2(root);
243
- if (st.isFile()) {
244
- const resolved = await realpath(root).catch(() => null);
245
- if (isSensitivePath(root) || resolved !== null && isSensitivePath(resolved)) {
246
- return {
247
- content: `Refused to grep sensitive file: ${args.path ?? "."}`,
248
- isError: true
249
- };
250
- }
251
- files = [{ abs: root, rel: path3.basename(root), mtimeMs: st.mtimeMs }];
252
- } else {
253
- const walked = await walkFiles(root, void 0, {
254
- ignore: createIgnorePredicate(ctx.cwd),
255
- withMtime: false,
256
- signal: ctx.abortSignal,
257
- symlinkBoundary: ctx.cwd,
258
- nestedIgnore: true
259
- });
260
- interrupted = walked.aborted;
261
- files = walked.files.filter(
262
- (f) => !isSensitivePath(f.abs) && (f.resolved === void 0 || !isSensitivePath(f.resolved))
263
- );
264
- }
265
- } catch {
266
- return { content: `Path not found: ${args.path ?? "."}`, isError: true };
267
- }
268
- if (args.glob) {
269
- const glob = args.glob;
270
- files = files.filter((f) => matchesGlob(glob, f.rel));
271
- }
276
+ const out = [];
277
+ let truncated = false;
278
+ let skippedLarge = 0;
272
279
  const scanFile = async (file) => {
273
280
  const st = await stat2(file.abs).catch(() => null);
274
- if (!st) {
275
- return null;
276
- }
281
+ if (!st) return null;
277
282
  if (st.size > MAX_FILE_BYTES) {
278
283
  skippedLarge += 1;
279
284
  return null;
@@ -284,12 +289,10 @@ var grepTool = {
284
289
  } catch {
285
290
  return null;
286
291
  }
287
- if (buf.includes(0)) {
288
- return null;
289
- }
292
+ if (buf.includes(0)) return null;
290
293
  const hits = [];
291
294
  const lines = buf.toString("utf8").split("\n");
292
- for (let i = 0; i < lines.length; i++) {
295
+ for (let i = 0; i < lines.length && hits.length < MAX_MATCHES; i += 1) {
293
296
  const line = lines[i] ?? "";
294
297
  if (re.test(line)) {
295
298
  const shown = line.length > MAX_LINE_CHARS ? `${line.slice(0, MAX_LINE_CHARS)}\u2026` : line;
@@ -298,29 +301,55 @@ var grepTool = {
298
301
  }
299
302
  return hits;
300
303
  };
301
- const out = [];
302
- let truncated = false;
303
- let skippedLarge = 0;
304
- outer: for (let i = 0; i < files.length; i += WALK_CONCURRENCY) {
305
- if (ctx.abortSignal?.aborted) {
306
- interrupted = true;
307
- break;
308
- }
309
- const chunk = await Promise.all(
310
- files.slice(i, i + WALK_CONCURRENCY).map((file) => scanFile(file))
304
+ const scanBatch = async (batch) => {
305
+ const files = batch.filter(
306
+ (f) => !isSensitivePath(f.abs) && (f.resolved === void 0 || !isSensitivePath(f.resolved)) && (!args.glob || matchesGlob(args.glob, f.rel))
311
307
  );
312
- for (const hits of chunk) {
313
- if (!hits) {
314
- continue;
308
+ outer: for (let i = 0; i < files.length; i += WALK_CONCURRENCY) {
309
+ if (ctx.abortSignal?.aborted) {
310
+ interrupted = true;
311
+ return false;
315
312
  }
316
- for (const hit of hits) {
317
- out.push(hit);
318
- if (out.length >= MAX_MATCHES) {
319
- truncated = true;
320
- break outer;
313
+ const chunk = await Promise.all(
314
+ files.slice(i, i + WALK_CONCURRENCY).map((file) => scanFile(file))
315
+ );
316
+ for (const hits of chunk) {
317
+ for (const hit of hits ?? []) {
318
+ out.push(hit);
319
+ if (out.length >= MAX_MATCHES) {
320
+ truncated = true;
321
+ break outer;
322
+ }
321
323
  }
322
324
  }
323
325
  }
326
+ return !truncated && !ctx.abortSignal?.aborted;
327
+ };
328
+ try {
329
+ const st = await stat2(root);
330
+ if (st.isFile()) {
331
+ const resolved = await realpath(root).catch(() => null);
332
+ if (isSensitivePath(root) || resolved !== null && isSensitivePath(resolved)) {
333
+ return {
334
+ content: `Refused to grep sensitive file: ${args.path ?? "."}`,
335
+ isError: true
336
+ };
337
+ }
338
+ await scanBatch([{ abs: root, rel: path3.basename(root), mtimeMs: st.mtimeMs }]);
339
+ } else {
340
+ const walked = await walkFiles(root, void 0, {
341
+ ignore: createIgnorePredicate(ctx.cwd),
342
+ withMtime: false,
343
+ signal: ctx.abortSignal,
344
+ symlinkBoundary: ctx.cwd,
345
+ nestedIgnore: true,
346
+ collect: false,
347
+ onBatch: scanBatch
348
+ });
349
+ interrupted = walked.aborted;
350
+ }
351
+ } catch {
352
+ return { content: `Path not found: ${args.path ?? "."}`, isError: true };
324
353
  }
325
354
  const skippedNote = skippedLarge > 0 ? `(skipped ${skippedLarge} file(s) larger than 5MB)` : null;
326
355
  if (out.length === 0) {
@@ -583,38 +612,82 @@ var codeOutlineTool = {
583
612
  // src/tools/fs/read.ts
584
613
  import { open, realpath as realpath3, stat as stat4 } from "fs/promises";
585
614
  import path5 from "path";
615
+ import { StringDecoder } from "string_decoder";
586
616
  import { z as z5 } from "zod";
587
617
  var DEFAULT_LIMIT = 2e3;
588
618
  var MAX_CHARS = 100 * 1024;
589
- var MAX_READ_BYTES = 1024 * 1024;
590
619
  var BINARY_PROBE_BYTES2 = 8192;
591
- function decodeUtf16be(buf) {
592
- const len = buf.length - buf.length % 2;
593
- const swapped = Buffer.allocUnsafe(len);
594
- for (let k = 0; k < len; k += 2) {
595
- swapped[k] = buf[k + 1] ?? 0;
596
- swapped[k + 1] = buf[k] ?? 0;
597
- }
598
- return swapped.toString("utf16le");
599
- }
600
- function decodeTextBuffer(buf) {
601
- if (buf.length >= 2 && buf[0] === 255 && buf[1] === 254) {
602
- return { text: buf.subarray(2).toString("utf16le") };
603
- }
604
- if (buf.length >= 2 && buf[0] === 254 && buf[1] === 255) {
605
- return { text: decodeUtf16be(buf.subarray(2)) };
606
- }
607
- if (buf.subarray(0, BINARY_PROBE_BYTES2).includes(0)) {
608
- return { binary: true };
609
- }
610
- return { text: buf.toString("utf8") };
611
- }
620
+ var READ_CHUNK_BYTES = 64 * 1024;
612
621
  var schema5 = z5.object({
613
622
  path: z5.string().describe("File path, absolute or relative to the working directory"),
614
623
  offset: z5.number().int().positive().optional().describe("1-based line number to start reading from"),
615
624
  limit: z5.number().int().positive().optional().describe("Maximum number of lines to read (default 2000)")
616
625
  });
617
626
  var READ_CACHE_MAX_ENTRIES = 200;
627
+ async function readWindow(handle, size, start, limit) {
628
+ const probe = Buffer.alloc(Math.min(size, BINARY_PROBE_BYTES2));
629
+ const { bytesRead: probeBytes } = await handle.read(probe, 0, probe.length, 0);
630
+ const head = probe.subarray(0, probeBytes);
631
+ const utf16le = head[0] === 255 && head[1] === 254;
632
+ const utf16be = head[0] === 254 && head[1] === 255;
633
+ if (!utf16le && !utf16be && head.includes(0)) return { binary: true };
634
+ const decoder = new StringDecoder(utf16le || utf16be ? "utf16le" : "utf8");
635
+ const requestedEnd = start - 1 + limit;
636
+ const result = {
637
+ totalLines: 1,
638
+ out: [],
639
+ lastShown: start - 1,
640
+ truncatedBySize: false
641
+ };
642
+ let lineNumber = 1;
643
+ let outputSize = 0;
644
+ let pending = "";
645
+ let beRemainder;
646
+ const showLine = (raw) => {
647
+ if (lineNumber >= start && lineNumber <= requestedEnd && !result.truncatedBySize) {
648
+ const numbered = `${lineNumber} ${raw.replace(/\r$/, "")}`;
649
+ if (outputSize + numbered.length > MAX_CHARS) {
650
+ result.truncatedBySize = true;
651
+ } else {
652
+ result.out.push(numbered);
653
+ outputSize += numbered.length;
654
+ result.lastShown = lineNumber;
655
+ }
656
+ }
657
+ };
658
+ const accept = (text) => {
659
+ const parts = `${pending}${text}`.split("\n");
660
+ for (let i = 0; i < parts.length - 1; i += 1) {
661
+ showLine(parts[i] ?? "");
662
+ lineNumber += 1;
663
+ }
664
+ pending = parts[parts.length - 1] ?? "";
665
+ };
666
+ let position = utf16le || utf16be ? 2 : 0;
667
+ while (position < size) {
668
+ const length = Math.min(READ_CHUNK_BYTES, size - position);
669
+ const { bytesRead, buffer } = await handle.read(Buffer.alloc(length), 0, length, position);
670
+ if (bytesRead === 0) break;
671
+ position += bytesRead;
672
+ let chunk = buffer.subarray(0, bytesRead);
673
+ if (utf16be) {
674
+ const combined = beRemainder === void 0 ? chunk : Buffer.concat([Buffer.from([beRemainder]), chunk]);
675
+ const pairedLength = combined.length - combined.length % 2;
676
+ const swapped = Buffer.allocUnsafe(pairedLength);
677
+ for (let i = 0; i < pairedLength; i += 2) {
678
+ swapped[i] = combined[i + 1] ?? 0;
679
+ swapped[i + 1] = combined[i] ?? 0;
680
+ }
681
+ beRemainder = pairedLength < combined.length ? combined[combined.length - 1] : void 0;
682
+ chunk = swapped;
683
+ }
684
+ accept(decoder.write(chunk));
685
+ }
686
+ accept(decoder.end());
687
+ showLine(pending);
688
+ result.totalLines = lineNumber;
689
+ return result;
690
+ }
618
691
  function createReadFileTool() {
619
692
  const cache = /* @__PURE__ */ new Map();
620
693
  return {
@@ -648,55 +721,33 @@ function createReadFileTool() {
648
721
  };
649
722
  }
650
723
  }
651
- const bytesToRead = Math.min(st.size, MAX_READ_BYTES);
652
- const fileCapped = st.size > MAX_READ_BYTES;
653
724
  const handle = await open(filePath, "r").catch(() => null);
654
725
  if (!handle) {
655
726
  return { content: `Failed to read ${args.path}`, isError: true };
656
727
  }
657
- let buf;
728
+ let decoded;
658
729
  try {
659
- const { bytesRead, buffer } = await handle.read(
660
- Buffer.alloc(bytesToRead),
661
- 0,
662
- bytesToRead,
663
- 0
664
- );
665
- buf = buffer.subarray(0, bytesRead);
730
+ decoded = await readWindow(handle, st.size, start, limit);
731
+ } catch {
732
+ return { content: `Failed to read ${args.path}`, isError: true };
666
733
  } finally {
667
734
  await handle.close().catch(() => {
668
735
  });
669
736
  }
670
- const decoded = decodeTextBuffer(buf);
671
737
  if ("binary" in decoded) {
672
738
  return { content: `Cannot read binary file: ${args.path}`, isError: true };
673
739
  }
674
- const lines = decoded.text.split("\n");
675
- const totalDesc = fileCapped ? `at least ${lines.length}` : `${lines.length}`;
676
- if (start > lines.length) {
677
- return { content: `(offset ${start} beyond end of file; file has ${totalDesc} lines)` };
740
+ const { totalLines, out, lastShown, truncatedBySize } = decoded;
741
+ if (start > totalLines) {
742
+ return { content: `(offset ${start} beyond end of file; file has ${totalLines} lines)` };
678
743
  }
679
- const end = Math.min(lines.length, start - 1 + limit);
680
- const out = [];
681
- let size = 0;
682
- let truncatedBySize = false;
683
- for (let i = start - 1; i < end; i++) {
684
- const line = (lines[i] ?? "").replace(/\r$/, "");
685
- const numbered = `${i + 1} ${line}`;
686
- if (size + numbered.length > MAX_CHARS) {
687
- truncatedBySize = true;
688
- break;
689
- }
690
- out.push(numbered);
691
- size += numbered.length;
692
- }
693
- const lastShown = start - 1 + out.length;
744
+ const end = Math.min(totalLines, start - 1 + limit);
694
745
  if (truncatedBySize) {
695
746
  out.push(
696
- `... (truncated: output exceeds 100KB, showing lines ${start}-${lastShown} of ${totalDesc})`
747
+ `... (truncated: output exceeds 100KB, showing lines ${start}-${lastShown} of ${totalLines})`
697
748
  );
698
- } else if (end < lines.length || fileCapped) {
699
- out.push(`... (truncated: showing lines ${start}-${end} of ${totalDesc})`);
749
+ } else if (end < totalLines) {
750
+ out.push(`... (truncated: showing lines ${start}-${end} of ${totalLines})`);
700
751
  }
701
752
  if (out.length > 0) {
702
753
  if (cache.size >= READ_CACHE_MAX_ENTRIES) cache.clear();
@@ -704,7 +755,7 @@ function createReadFileTool() {
704
755
  cache.set(cacheKey, {
705
756
  mtimeMs: st.mtimeMs,
706
757
  size: st.size,
707
- totalLines: lines.length,
758
+ totalLines,
708
759
  // Extend the remembered range when the new read overlaps or abuts
709
760
  // the previous one; disjoint ranges replace it.
710
761
  start: prev && start <= prev.end + 1 ? Math.min(prev.start, start) : start,
@@ -834,6 +885,13 @@ var writeFileTool = {
834
885
  };
835
886
  }
836
887
  const snapshot = await captureSnapshot(filePath, "write_file", ctx.snapshotContext);
888
+ if (snapshot.existed && snapshot.content === null) {
889
+ const reason = snapshot.contentTooLarge ? "existing content is over the 5MB snapshot limit" : "existing content could not be read";
890
+ return {
891
+ content: `Refused to overwrite ${args.path}: ${reason}, and no restorable snapshot is available`,
892
+ isError: true
893
+ };
894
+ }
837
895
  try {
838
896
  await mkdir(path7.dirname(filePath), { recursive: true });
839
897
  await writeFile2(filePath, args.content, "utf8");
@@ -1155,6 +1213,7 @@ function debugStreamLog(event, data) {
1155
1213
 
1156
1214
  // src/tools/web/search.ts
1157
1215
  var TIMEOUT_MS = 15e3;
1216
+ var MAX_BODY_BYTES = 2 * 1024 * 1024;
1158
1217
  var DEFAULT_MAX_RESULTS = 5;
1159
1218
  var MAX_RESULTS_LIMIT = 10;
1160
1219
  var USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
@@ -1277,6 +1336,26 @@ var SEARCH_SOURCES = [
1277
1336
  parse: parseBingResults
1278
1337
  }
1279
1338
  ];
1339
+ async function readLimitedBody(res) {
1340
+ if (!res.body) return "";
1341
+ const reader = res.body.getReader();
1342
+ const decoder = new TextDecoder();
1343
+ let bytes = 0;
1344
+ let text = "";
1345
+ try {
1346
+ while (bytes < MAX_BODY_BYTES) {
1347
+ const { done, value } = await reader.read();
1348
+ if (done) break;
1349
+ bytes += value.byteLength;
1350
+ text += decoder.decode(value, { stream: true });
1351
+ }
1352
+ text += decoder.decode();
1353
+ } finally {
1354
+ await reader.cancel().catch(() => {
1355
+ });
1356
+ }
1357
+ return text;
1358
+ }
1280
1359
  var webSearchTool = {
1281
1360
  name: "web_search",
1282
1361
  description: "Search the web and return a numbered list of results with title, URL and snippet. Uses DuckDuckGo with automatic fallback to DuckDuckGo Lite and Bing when a source fails or returns nothing. No API key required. Use web_fetch to read a result's full page.",
@@ -1316,7 +1395,7 @@ var webSearchTool = {
1316
1395
  }
1317
1396
  let html;
1318
1397
  try {
1319
- html = await res.text();
1398
+ html = await readLimitedBody(res);
1320
1399
  } catch (err) {
1321
1400
  const message = err instanceof Error ? err.message : String(err);
1322
1401
  failures.push(`${source.name}: failed to read search response: ${message}`);
@@ -1424,4 +1503,4 @@ export {
1424
1503
  ToolRegistry,
1425
1504
  createDefaultRegistry
1426
1505
  };
1427
- //# sourceMappingURL=chunk-OF42OVM4.js.map
1506
+ //# sourceMappingURL=chunk-54KH4JSO.js.map