@underactive/pi-topping-web-tools 0.2.2 → 0.2.3

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,17 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.2.3] - 2026-09-20
6
+
7
+ ### Changed
8
+
9
+ - Bumped the pinned `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` devDependencies from 0.82.1 to 0.86.0 so `npm run typecheck` matches the 0.86.0 runtime API.
10
+
11
+ ### Fixed
12
+
13
+ - `web_browser`, `pdf_extract`: tool result `details` no longer carry undefined-valued properties, keeping them JSON-compatible as pi 0.86.0's `ToolResultMessage.details` requires. `web_browser` omits `statusCode` for same-document navigations; `pdf_extract` omits `firstPage`/`lastPage` for a zero-page PDF and no longer prints "pages undefined-undefined" in its no-text-layer message.
14
+ - `pdf_extract`: strip `</untrusted-content` fence breaks case-insensitively so mixed-case closing tags cannot bypass sanitization.
15
+
5
16
  ## [0.2.2] - 2026-09-02
6
17
 
7
18
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@underactive/pi-topping-web-tools",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
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.82.1",
57
- "@earendil-works/pi-coding-agent": "0.82.1",
58
- "@earendil-works/pi-tui": "0.82.1",
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",
59
59
  "@types/node": "^24.0.0",
60
60
  "@types/turndown": "^5.0.5",
61
61
  "typebox": "^1.3.3",
@@ -48,10 +48,6 @@ export function clearPdfExtractCache(): void {
48
48
 
49
49
  const _activeFetches = new Set<AbortController>();
50
50
 
51
- export function _activeFetchCountForTests(): number {
52
- return _activeFetches.size;
53
- }
54
-
55
51
  export function _cleanup(): void {
56
52
  PDF_CACHE.clear();
57
53
  for (const controller of _activeFetches) {
@@ -342,12 +338,14 @@ Usage notes:
342
338
  .join("\n\n");
343
339
 
344
340
  const hasText = trimmed.some((text) => text.length > 0);
341
+ const range =
342
+ selected.length > 0 ? { firstPage: selected[0], lastPage: selected[selected.length - 1] } : undefined;
345
343
  const baseDetails = {
346
344
  source,
347
345
  totalPages: loaded.totalPages,
348
346
  pageCount: selected.length,
349
- firstPage: selected[0],
350
- lastPage: selected[selected.length - 1],
347
+ // Empty only for a zero-page PDF; omit the keys rather than storing undefined.
348
+ ...(range ?? {}),
351
349
  bytes: loaded.bytes,
352
350
  durationMs: Date.now() - start,
353
351
  cached: loaded.cached,
@@ -358,7 +356,9 @@ Usage notes:
358
356
  content: [
359
357
  {
360
358
  type: "text" as const,
361
- text: `No extractable text layer found in ${source} (pages ${baseDetails.firstPage}-${baseDetails.lastPage} of ${loaded.totalPages}). The PDF is likely scanned or image-only. This tool does not perform OCR.`,
359
+ text: `No extractable text layer found in ${source}${
360
+ range ? ` (pages ${range.firstPage}-${range.lastPage} of ${loaded.totalPages})` : ` (0 of ${loaded.totalPages} page(s))`
361
+ }. The PDF is likely scanned or image-only. This tool does not perform OCR.`,
362
362
  },
363
363
  ],
364
364
  details: baseDetails satisfies PdfExtractDetails,
@@ -367,7 +367,7 @@ Usage notes:
367
367
 
368
368
  const offset = Math.max(0, Math.floor(params.offset ?? 0));
369
369
  const slice = sliceContent(body, offset);
370
- const sanitizedText = slice.text.replaceAll("</untrusted-content", "");
370
+ const sanitizedText = slice.text.replaceAll(/<\/untrusted-content/gi, "");
371
371
  const safeSource = source.replaceAll('"', "%22");
372
372
 
373
373
  return {
@@ -516,7 +516,8 @@ export default function (pi: ExtensionAPI) {
516
516
  action,
517
517
  url: result.url,
518
518
  title: result.title,
519
- statusCode: result.statusCode,
519
+ // A same-document navigation has no response; omit the key rather than storing undefined.
520
+ ...(result.statusCode !== undefined ? { statusCode: result.statusCode } : {}),
520
521
  bytes: Buffer.byteLength(text),
521
522
  } satisfies WebBrowserDetails,
522
523
  };