@zeldrisho/pi-web-fetch 0.13.0 → 0.14.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
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.14.0] - 2026-10-25
11
+
12
+ ### Changed
13
+
14
+ - Show cache progress, focus queries, offsets, and a compact fetch summary in Pi's tool preview without adding model-facing output.
15
+ - Declare read-only network access hints for permission integrations and validate existing input limits in the tool schema.
16
+
10
17
  ## [0.13.0] - 2026-10-24
11
18
 
12
19
  ### Added
@@ -256,7 +263,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
256
263
 
257
264
  - Add bounded public page fetching ([b979496](https://github.com/zeldrisho/pi-packages/commit/b979496b32de1cead172ba570307e4a4a7b3421d))
258
265
 
259
- [Unreleased]: https://github.com/zeldrisho/pi-packages/compare/pi-web-fetch-v0.13.0...HEAD
266
+ [Unreleased]: https://github.com/zeldrisho/pi-packages/compare/pi-web-fetch-v0.14.0...HEAD
267
+ [0.14.0]: https://github.com/zeldrisho/pi-packages/compare/pi-web-fetch-v0.13.0...pi-web-fetch-v0.14.0
260
268
  [0.13.0]: https://github.com/zeldrisho/pi-packages/compare/pi-web-fetch-v0.12.0...pi-web-fetch-v0.13.0
261
269
  [0.12.0]: https://github.com/zeldrisho/pi-packages/compare/pi-web-fetch-v0.11.1...pi-web-fetch-v0.12.0
262
270
  [0.11.1]: https://github.com/zeldrisho/pi-packages/compare/pi-web-fetch-v0.11.0...pi-web-fetch-v0.11.1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeldrisho/pi-web-fetch",
3
- "version": "0.13.0",
3
+ "version": "0.14.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
@@ -1,9 +1,14 @@
1
1
  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
+ import {
5
+ FETCH_MAX_OFFSET_CHARACTERS,
6
+ FETCH_MAX_QUERY_CHARACTERS,
7
+ FETCH_MAX_URL_CHARACTERS,
8
+ } from "./limits";
4
9
  import { redactUrlForDisplay } from "./redact";
5
10
  import { formatCollapsibleOutput } from "./render";
6
- import { executeWebFetch, webFetchOutputSchema } from "./service";
11
+ import { executeWebFetch, webFetchOutputSchema, type WebFetchDetails } from "./service";
7
12
 
8
13
  export { ExpiringLruCache } from "./cache";
9
14
 
@@ -62,9 +67,21 @@ export {
62
67
  } from "./network/network.ts";
63
68
 
64
69
  export const webFetchParameters = Type.Object({
65
- url: Type.String(),
66
- query: Type.Optional(Type.String({ description: "Find sections matching this text." })),
67
- offset: Type.Optional(Type.Integer({ description: "Start at this character offset." })),
70
+ url: Type.String({ minLength: 1, maxLength: FETCH_MAX_URL_CHARACTERS }),
71
+ query: Type.Optional(
72
+ Type.String({
73
+ minLength: 1,
74
+ maxLength: FETCH_MAX_QUERY_CHARACTERS,
75
+ description: "Find sections matching this text.",
76
+ }),
77
+ ),
78
+ offset: Type.Optional(
79
+ Type.Integer({
80
+ minimum: 0,
81
+ maximum: FETCH_MAX_OFFSET_CHARACTERS,
82
+ description: "Start at this character offset.",
83
+ }),
84
+ ),
68
85
  });
69
86
 
70
87
  /**
@@ -73,16 +90,18 @@ export const webFetchParameters = Type.Object({
73
90
  * @param pi - The extension API instance
74
91
  */
75
92
  export default function (pi: ExtensionAPI) {
76
- pi.registerTool({
93
+ pi.registerTool<typeof webFetchParameters, Partial<WebFetchDetails>>({
77
94
  name: "web_fetch",
78
95
  label: "Web Fetch",
79
96
  description: "Read a public webpage as Markdown.",
80
97
  parameters: webFetchParameters,
81
98
  outputSchema: webFetchOutputSchema,
99
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
82
100
 
101
+ /** Renders the redacted URL, optional focus query, and character offset in the call preview. */
83
102
  renderCall(args, theme) {
84
103
  return new Text(
85
- `${theme.fg("toolTitle", theme.bold("web_fetch"))} ${theme.fg("accent", redactUrlForDisplay(args.url))}`,
104
+ `${theme.fg("toolTitle", theme.bold("web_fetch"))} ${theme.fg("accent", redactUrlForDisplay(args.url))}${args.query === undefined ? "" : ` query=${JSON.stringify(args.query)}`}${args.offset === undefined ? "" : ` offset=${args.offset}`}`,
86
105
  0,
87
106
  0,
88
107
  );
@@ -92,14 +111,34 @@ export default function (pi: ExtensionAPI) {
92
111
  return executeWebFetch(params, signal, onUpdate);
93
112
  },
94
113
 
114
+ /**
115
+ * Renders partial text or "Fetching…" while pending. Completed text gets a
116
+ * detail summary when available and a collapsible body; absent text shows "No content".
117
+ */
95
118
  renderResult(result, { expanded, isPartial }, theme) {
96
- if (isPartial) return new Text(theme.fg("warning", "Fetching…"), 0, 0);
97
-
98
119
  const content = result.content.find((item) => item.type === "text");
99
120
 
121
+ if (isPartial) return new Text(theme.fg("warning", content?.text || "Fetching…"), 0, 0);
122
+
123
+ const details = result.details;
124
+
125
+ const summary =
126
+ details?.characterCount !== undefined
127
+ ? [
128
+ details.extractionDiagnostics?.botWall
129
+ ? "Blocked"
130
+ : `${details.characterCount} characters`,
131
+ details.cacheStatus ?? (details.cached ? "cached" : "miss"),
132
+ ...(details.focus
133
+ ? [`${details.focus.matchedSections}/${details.focus.totalSections} sections`]
134
+ : []),
135
+ ...(details.nextOffset === undefined ? [] : [`next offset=${details.nextOffset}`]),
136
+ ].join(" · ")
137
+ : "";
138
+
100
139
  return new Text(
101
140
  content?.type === "text"
102
- ? formatCollapsibleOutput(content.text, expanded, theme)
141
+ ? `${theme.fg("dim", summary)}${formatCollapsibleOutput(content.text, expanded, theme)}`
103
142
  : theme.fg("dim", "No content"),
104
143
  0,
105
144
  0,