@zeldrisho/pi-web-fetch 0.4.0 → 0.5.0

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/CHANGELOG.md CHANGED
@@ -1,4 +1,16 @@
1
1
  # Changelog
2
+ ## [0.5.0](https://github.com/zeldrisho/pi-packages/compare/pi-web-fetch-v0.4.0...pi-web-fetch-v0.5.0) (2026-07-28)
3
+
4
+
5
+ ### Bug fixes
6
+
7
+ - Apply CodeRabbit auto-fixes ([b9149ac](https://github.com/zeldrisho/pi-packages/commit/b9149ac91016bb25b49806af5c99b8486420dd74))
8
+
9
+
10
+ ### Features
11
+
12
+ - **web-fetch:** Support larger documentation pages ([39cdb32](https://github.com/zeldrisho/pi-packages/commit/39cdb3213a9eee9e0c905319631ef3228e31c8d8))
13
+
2
14
 
3
15
  ## [0.4.0](https://github.com/zeldrisho/pi-packages/compare/pi-web-fetch-v0.3.1...pi-web-fetch-v0.4.0) (2026-07-25)
4
16
 
package/README.md CHANGED
@@ -12,9 +12,9 @@ pi install npm:@zeldrisho/pi-web-fetch
12
12
 
13
13
  The `web_fetch` tool accepts public HTTP and HTTPS URLs. It supports textual content such as HTML, Markdown, plain text, JSON, and XML. HTML pages are converted to Markdown with Defuddle; a basic text extractor is used as a fallback when Defuddle cannot extract the page.
14
14
 
15
- For safety, the tool blocks URLs containing credentials, local hostnames, private or reserved network targets, unsafe redirects, responses larger than its configured limit, and unsupported content types.
15
+ For safety, the tool blocks URLs containing credentials, local hostnames, private or reserved network targets, unsafe redirects, raw responses larger than 5 MiB, and unsupported content types. The `maxCharacters` parameter controls returned Markdown length; it does not change the raw download limit.
16
16
 
17
- In Pi's interactive UI, fetched content uses Pi's standard collapsed preview; use the configured tool-expansion shortcut (`Ctrl+O` by default) to show all visible tool output. Output sent to the agent remains bounded. When a result is truncated, call the tool again with the returned `nextOffset` as `offset` to continue reading. Fetched and extracted pages are cached in byte-bounded memory for a limited time so continuation requests can reuse the same content. Concurrent requests for the same URL share one fetch; cancelling one caller does not cancel work still needed by another.
17
+ In Pi's interactive UI, fetched content uses Pi's standard collapsed preview; use the configured tool-expansion shortcut (`Ctrl+O` by default) to show all visible tool output. Output sent to the agent remains bounded. The `offset` parameter is a character offset into extracted content, not a byte range into the remote response. When a result is truncated, call the tool again with the returned `nextOffset` as `offset` to continue reading. Fetched and extracted pages are cached in byte-bounded memory for a limited time so continuation requests can reuse the same content. Concurrent requests for the same URL share one fetch; cancelling one caller does not cancel work still needed by another.
18
18
 
19
19
  Every result includes `details.truncation`. Complete output reports `{ truncated: false, strategy: "none" }`. Truncated output reports `strategy: "continuation"` and a valid `nextOffset`. The existing top-level `details.truncated` and `details.nextOffset` fields remain available.
20
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeldrisho/pi-web-fetch",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Pi extension for secure, bounded public web page fetching and Markdown extraction",
5
5
  "keywords": [
6
6
  "pi",
package/src/index.ts CHANGED
@@ -2,7 +2,6 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { Text } from "@earendil-works/pi-tui";
3
3
  import { Type } from "typebox";
4
4
  import { executeWebFetch } from "./service";
5
- import { FETCH_MAX_BYTES } from "./network";
6
5
  import { formatCollapsibleOutput } from "./render";
7
6
 
8
7
  export { ExpiringLruCache } from "./cache";
@@ -15,6 +14,7 @@ export {
15
14
  type WebFetchTruncationDetails,
16
15
  } from "./service";
17
16
  export {
17
+ FETCH_MAX_BYTES,
18
18
  isPrivateAddress,
19
19
  requestPinned,
20
20
  validateRemoteUrl,
@@ -22,6 +22,7 @@ export {
22
22
  } from "./network";
23
23
 
24
24
  const FETCH_DEFAULT_MAX_CHARACTERS = 6_000;
25
+ const FETCH_MAX_OFFSET_CHARACTERS = 20_000_000;
25
26
 
26
27
  export default function (pi: ExtensionAPI) {
27
28
  pi.registerTool({
@@ -44,9 +45,9 @@ export default function (pi: ExtensionAPI) {
44
45
  offset: Type.Optional(
45
46
  Type.Integer({
46
47
  minimum: 0,
47
- maximum: FETCH_MAX_BYTES,
48
+ maximum: FETCH_MAX_OFFSET_CHARACTERS,
48
49
  description:
49
- "Character offset to start reading from (default: 0; use nextOffset to continue)",
50
+ "Extracted-content character offset to start reading from (default: 0; use nextOffset to continue)",
50
51
  }),
51
52
  ),
52
53
  maxCharacters: Type.Optional(
@@ -4,10 +4,24 @@ import type { LookupFunction } from "node:net";
4
4
  import { formatSize } from "@earendil-works/pi-coding-agent";
5
5
  import type { ValidatedTarget } from "./network-policy";
6
6
 
7
- export const FETCH_MAX_BYTES = 1_000_000;
7
+ export const FETCH_MAX_BYTES = 5 * 1_024 * 1_024;
8
8
 
9
9
  const encoder = new TextEncoder();
10
10
 
11
+ function responseTooLargeMessage(
12
+ receivedBytes: number,
13
+ maxBytes: number,
14
+ sizeIsExact: boolean,
15
+ ): string {
16
+ const size = sizeIsExact
17
+ ? `is ${formatSize(receivedBytes)}`
18
+ : `has reached at least ${formatSize(receivedBytes)}`;
19
+ return [
20
+ `web_fetch response ${size}, exceeding the ${formatSize(maxBytes)} raw download limit.`,
21
+ "maxCharacters only controls returned output.",
22
+ ].join(" ");
23
+ }
24
+
11
25
  export async function requestPinned(
12
26
  target: ValidatedTarget,
13
27
  signal: AbortSignal,
@@ -45,8 +59,10 @@ export async function readResponseBytes(
45
59
  maxBytes: number,
46
60
  ): Promise<Uint8Array> {
47
61
  const declared = Number(responseHeader(response, "content-length"));
48
- if (Number.isFinite(declared) && declared > maxBytes)
49
- throw new Error(`web_fetch response exceeds ${formatSize(maxBytes)}.`);
62
+ if (Number.isFinite(declared) && declared > maxBytes) {
63
+ response.destroy();
64
+ throw new Error(responseTooLargeMessage(declared, maxBytes, true));
65
+ }
50
66
  const chunks: Uint8Array[] = [];
51
67
  let total = 0;
52
68
  for await (const value of response) {
@@ -54,7 +70,7 @@ export async function readResponseBytes(
54
70
  total += chunk.byteLength;
55
71
  if (total > maxBytes) {
56
72
  response.destroy();
57
- throw new Error(`web_fetch response exceeds ${formatSize(maxBytes)}.`);
73
+ throw new Error(responseTooLargeMessage(total, maxBytes, false));
58
74
  }
59
75
  chunks.push(chunk);
60
76
  }