@underactive/pi-topping-web-tools 0.2.3 → 0.2.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/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.2.4] - 2026-09-29
6
+
7
+ ### Added
8
+
9
+ - Documented pi 0.87+ `inputLimits.images.resize` for `web_browser` inline screenshots: how the active model's resize profile applies to tool-result images, a cost-saving example profile, the resize note appended to resized images, and a warning that aggressive resizing can make small page text unreadable.
10
+ - `fetch_markdown`, `pdf_extract`, `web_browser`: declare pi 0.99 tool `annotations` so permission extensions can decide which calls to confirm. `fetch_markdown` and `pdf_extract` set `readOnlyHint` and `openWorldHint`; `web_browser` sets `openWorldHint` and `readOnlyHint: false`. Older pi versions ignore the field.
11
+
12
+ ### Changed
13
+
14
+ - Tested against pi 0.99; host-permission gates also apply to codemode script calls.
15
+ - Bumped the pinned `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` devDependencies from 0.86.0 to 0.99.1 so `npm run typecheck` accepts tool `annotations`.
16
+
5
17
  ## [0.2.3] - 2026-09-20
6
18
 
7
19
  ### Changed
package/README.md CHANGED
@@ -129,6 +129,47 @@ Single tool with an `action` parameter:
129
129
 
130
130
  Optional `timeout` (ms, default 30000) applies to navigation and to selector-based actions (click, type, hover, select_option, screenshot, get_text, get_markdown, wait_for, get_accessibility_snapshot, scroll, drag, upload_file). Other actions use Playwright defaults.
131
131
 
132
+ ### Screenshots and image resize
133
+
134
+ When `screenshot` returns an inline result (`toFile` omitted or `false`), `web_browser` places the captured PNG in the conversation as a base64 image block. Base64 adds roughly one-third to the payload, and full-page PNGs can be several MiB. On pi 0.87+, the active model's `inputLimits.images.resize` profile is applied to these tool-result images before they enter history; the same profile covers `@file` attachments, `read`, and images returned by other tools. Images are encoded once when they enter history, so changing models later does not rewrite historical images.
135
+
136
+ `toFile=true` and `/browser-screenshot` save the original PNG to disk and put only the path and dimensions in the tool result. The file is resized only if a later `read` or `@file` attachment sends it to the model.
137
+
138
+ The resize fields are:
139
+
140
+ - `maxWidth` / `maxHeight`: pixel bounds (pi defaults to 2000×2000)
141
+ - `maxBytes`: maximum base64-encoded payload size (pi defaults to 4.5 MiB)
142
+ - `jpegQuality`: JPEG quality when the PNG does not fit under `maxBytes` (pi defaults to 80)
143
+
144
+ The default capture is the viewport (1280×720 unless `set_viewport` changed it); `fullPage: true` captures the whole document. `web_browser` applies a 1920px maximum-width clamp to the reported dimensions of full-page screenshots; this is metadata only, not image resizing—the returned PNG remains the original capture. Tall full-page screenshots regularly exceed the default 2000px height bound and are scaled before they enter history; for normal viewport screenshots, `maxBytes` is usually the effective cost lever.
145
+
146
+ Configure a profile per model in `~/.pi/agent/models.json`; use the provider and model ID shown by `pi --list-models`. `modelOverrides.inputLimits` is deep-merged, so a resize override can be scoped to one model:
147
+
148
+ ```json
149
+ {
150
+ "providers": {
151
+ "<provider>": {
152
+ "modelOverrides": {
153
+ "<model-id>": {
154
+ "inputLimits": {
155
+ "images": {
156
+ "resize": {
157
+ "maxWidth": 1568,
158
+ "maxHeight": 1568,
159
+ "maxBytes": 524288,
160
+ "jpegQuality": 75
161
+ }
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }
167
+ }
168
+ }
169
+ ```
170
+
171
+ This matches pi's documented example. It leaves a default 1280×720 viewport screenshot unscaled on the long edge, making `maxBytes` the practical size lever. When pi resizes an image, it adds a coordinate note such as `[Image: original 1280x4000, displayed at 800x2500. Multiply coordinates by 1.60 to map to original image.]`; the tool's own `Screenshot <dims>` line is only a summary of the capture, so the resize note is the authoritative account of the dimensions sent to the model. An overly tight `maxBytes` does not fail loudly: pi keeps shrinking dimensions until the encoded image fits. Aggressive values can make small page text (navigation labels, fine print, or code) unreadable, and JPEG fallback adds artifacts; prefer `get_text`, `get_markdown`, or `get_accessibility_snapshot` when the task is reading copy. Set `images.autoResize: false` in pi's settings to disable resizing globally.
172
+
132
173
  ### Browser network egress
133
174
 
134
175
  `web_browser` enforces its permissions inside every fresh browser context:
@@ -197,6 +238,7 @@ Commands:
197
238
 
198
239
  - **Preapproved hosts**: common documentation and dev sites are allowed without prompting. The allowlist lives in `src/permissions.ts` and is shared by both tools.
199
240
  - **User confirmation**: other hosts prompt Allow once / Allow for this session / Allow for 1 day / Allow for 1 week / Allow for 30 days / Deny. For `web_browser` navigation, Allow once becomes a browser-session grant because request-level enforcement must authorize the loaded document's later requests.
241
+ - **Tool annotations**: on pi 0.99+, `fetch_markdown` and `pdf_extract` declare `readOnlyHint` and `openWorldHint`, and `web_browser` declares `openWorldHint` with `readOnlyHint: false`, for permission extensions that confirm calls by hint. The host confirmation above applies regardless of how another extension treats the hints.
200
242
  - **URL validation**: all tools reject embedded credentials and overlong URLs. `fetch_markdown` and `pdf_extract` additionally reject loopback, private, and link-local hosts and any non-HTTP(S) scheme on a `url`. `web_browser` accepts `localhost`, private IPs, and `file://` paths so it can drive local dev servers and local HTML — each still prompts for confirmation unless session-approved or covered by a saved durable grant.
201
243
  - **Local file resolution**: `pdf_extract` resolves a `path` through `realpath` before any check, so a symlink pointing outside the working directory is treated as outside and prompts. Directories, FIFOs, and devices are rejected, and the size limit is enforced from `stat` before any bytes are read. `web_browser` `upload_file` follows the same rule: files outside the working directory prompt for confirmation (session-scoped) before they are read.
202
244
  - **Isolated browser context and routed egress**: each session uses a fresh Chromium context (no shared profile/cookies with your system browser). HTTP(S) requests, redirect targets, client navigations, popups, and WebSocket connections are checked against live `web_browser` permissions; service workers are disabled.
@@ -206,7 +248,7 @@ Commands:
206
248
  ## Limitations
207
249
 
208
250
  - `web_browser` requires the Chromium download.
209
- - Screenshots consume significant context on vision models — prefer `get_text`, `get_markdown`, or `get_accessibility_snapshot`.
251
+ - Screenshots consume significant context on vision models — prefer `get_text`, `get_markdown`, or `get_accessibility_snapshot`. On pi 0.87+, a model-side resize profile ([Screenshots and image resize](#screenshots-and-image-resize)) can cut screenshot request size and cost; aggressive resizing can make small page text unreadable.
210
252
  - Network logs capture blocked/failed requests and 4xx/5xx responses, not successful traffic.
211
253
  - Request routing buffers HTTP(S) responses and blocks redirected subresources, so streaming endpoints and resources that rely on redirects may not behave like an unrestricted browser.
212
254
  - Playwright routing covers HTTP(S) and WebSockets, not browser transports such as WebRTC; OS-level egress sandboxing remains outside this extension's scope.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@underactive/pi-topping-web-tools",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Pi extensions for the web: fetch_markdown (URL to markdown, cached, keyless), pdf_extract (PDF text extraction) and web_browser (headless Playwright automation), sharing a preapproved-host allowlist.",
5
5
  "keywords": ["pi-package", "pi-extension"],
6
6
  "license": "MIT",
@@ -53,9 +53,9 @@
53
53
  "typebox": "*"
54
54
  },
55
55
  "devDependencies": {
56
- "@earendil-works/pi-ai": "0.86.0",
57
- "@earendil-works/pi-coding-agent": "0.86.0",
58
- "@earendil-works/pi-tui": "0.86.0",
56
+ "@earendil-works/pi-ai": "0.99.1",
57
+ "@earendil-works/pi-coding-agent": "0.99.1",
58
+ "@earendil-works/pi-tui": "0.99.1",
59
59
  "@types/node": "^24.0.0",
60
60
  "@types/turndown": "^5.0.5",
61
61
  "typebox": "^1.3.3",
@@ -476,6 +476,7 @@ Usage notes:
476
476
  "If fetch_markdown reports truncated content, call it again with the reported offset to read the next window.",
477
477
  ],
478
478
  parameters: WebFetchParams,
479
+ annotations: { readOnlyHint: true, openWorldHint: true },
479
480
 
480
481
  async execute(_toolCallId, params, signal, onUpdate) {
481
482
  const start = Date.now();
@@ -316,6 +316,7 @@ Usage notes:
316
316
  "If pdf_extract reports no text layer, the PDF is scanned — say so rather than retrying.",
317
317
  ],
318
318
  parameters: PdfExtractParams,
319
+ annotations: { readOnlyHint: true, openWorldHint: true },
319
320
 
320
321
  async execute(_toolCallId, params, signal, onUpdate) {
321
322
  const start = Date.now();
@@ -469,6 +469,8 @@ export default function (pi: ExtensionAPI) {
469
469
  Type.Number({ description: "Timeout in milliseconds for navigation/interaction (default 30000)" }),
470
470
  ),
471
471
  }),
472
+ // Not read-only: click, type, evaluate, set_cookies, and upload_file act on the page.
473
+ annotations: { readOnlyHint: false, openWorldHint: true },
472
474
 
473
475
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
474
476
  const action = params.action as WebBrowserAction;