@bacnh85/pi-web 0.6.1 → 0.6.2

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,6 +1,22 @@
1
1
  # Changelog
2
2
 
3
- ## 0.6.2 (2026-08-30)
3
+ ## 0.6.2 (2026-09-06)
4
+
5
+ ### Changed
6
+
7
+ - `web_screenshot` now returns the PNG **inline as an image block**
8
+ (`ImageContent`) alongside the text summary, so multimodal models (GLM-5.3,
9
+ Claude, Gemini) actually see the screenshot instead of a base64 char
10
+ count. The "Data: base64 PNG (N chars)" line is gone; artifact/MIME/size
11
+ summary unchanged. Inspired by zcode-plugins video2code's vision-in-the-loop.
12
+ Regression-tested in `test/unit/screenshot.test.ts` (fetch stubbed — no
13
+ daemon needed): image block present + base64-text line absent; text-only
14
+ fallback when the daemon returns no screenshot.
15
+
16
+ Daemon `success:false` responses (HTTP 200) now surface `error_message` as
17
+ a tool error instead of returning a silently empty screenshot result.
18
+
19
+ ## 0.6.1 (2026-08-30)
4
20
 
5
21
  ### Changed
6
22
 
@@ -11,9 +27,7 @@
11
27
  hook.test.ts assertion updated to the compressed phrasing. No tool,
12
28
  parameter, or default changed.
13
29
 
14
- ## 0.6.1 (2026-08-19)
15
-
16
- ### Changed
30
+ ### Changed (2026-08-19)
17
31
 
18
32
  - `web_extract` agy backend default model updated to `gemini-3.7-flash-medium`
19
33
  — the current Flash generation in agy 1.1.x (3.6 is still served, this just
package/README.md CHANGED
@@ -142,7 +142,7 @@ web_crawl url="https://example.com" mode=light poll=true # Poll for completio
142
142
 
143
143
  ### `web_screenshot` — Page screenshot
144
144
 
145
- Captures a full-page PNG screenshot using Crawl4AI. Returns base64-encoded PNG.
145
+ Captures a full-page PNG screenshot using Crawl4AI. Returns the PNG inline as an image block (multimodal models see it); text summary includes artifact/MIME/size.
146
146
 
147
147
  ```
148
148
  web_screenshot url="https://example.com"
@@ -286,9 +286,9 @@ export default function piWebExtension(pi: ExtensionAPI) {
286
286
  name: "web_screenshot",
287
287
  label: "Web Page Screenshot",
288
288
  description:
289
- "Full-page PNG screenshot via Crawl4AI.",
289
+ "Full-page PNG screenshot via Crawl4AI. The PNG is returned inline as an image block.",
290
290
  promptSnippet: "Screenshot a webpage",
291
- promptGuidelines: ["Full-page PNG; use when web_extract fails on JS-heavy pages."],
291
+ promptGuidelines: ["Full-page PNG returned inline (multimodal models see it); use when web_extract fails on JS-heavy pages, or to visually inspect a built UI."],
292
292
  parameters: Type.Object({
293
293
  url: Type.String(),
294
294
  wait_for: Type.Optional(Type.Number({ default: 2, description: "Seconds to wait before capture." })),
@@ -305,16 +305,23 @@ export default function piWebExtension(pi: ExtensionAPI) {
305
305
  params.wait_for_images as boolean | undefined,
306
306
  signal,
307
307
  );
308
+ if (result.success === false) {
309
+ throw new Error(String(result.error_message ?? "Crawl4AI screenshot failed"));
310
+ }
308
311
  const screenshot = result.screenshot as string | undefined;
309
312
  const artifactUrl = result.url as string | undefined;
310
313
  const mime = result.mime as string | undefined;
311
314
  const size = result.size as number | undefined;
312
315
  let text = `Screenshot: ${params.url}\n`;
313
- if (screenshot) text += `Data: base64 PNG (${screenshot.length} chars)\n`;
314
316
  if (artifactUrl) text += `Artifact: ${artifactUrl}\n`;
315
317
  if (mime) text += `MIME: ${mime}\n`;
316
318
  if (size) text += `Size: ${size} bytes\n`;
317
- return { content: [{ type: "text" as const, text: truncateText(text) }], details: { ...result, url: params.url } };
319
+ // Return the PNG as a real image block so multimodal models see it.
320
+ const content: Array<{ type: "text"; text: string } | { type: "image"; data: string; mimeType: string }> = [
321
+ { type: "text", text: truncateText(text) },
322
+ ];
323
+ if (screenshot) content.push({ type: "image", data: screenshot, mimeType: mime || "image/png" });
324
+ return { content, details: { ...result, url: params.url } };
318
325
  },
319
326
  });
320
327
 
@@ -59,6 +59,16 @@ export async function fetchReadableContent(
59
59
  signal: signalWithTimeout(timeoutMs, signal),
60
60
  });
61
61
  if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
62
+ // Raw text/JSON payloads (raw.githubusercontent.com, JSON APIs) — Readability
63
+ // shreds them to nothing. Pass through verbatim. text/html and text/xml keep
64
+ // the Readability path — they're ordinary web pages. Session mining: 9/40
65
+ // static extract failures were raw-text/JSON shapes.
66
+ const contentType = (response.headers.get("content-type") ?? "").split(";")[0].trim();
67
+ if ((contentType.startsWith("text/") && contentType !== "text/html" && contentType !== "text/xml") || contentType === "application/json") {
68
+ const body = await response.text();
69
+ const markdown = contentType === "application/json" ? "```json\n" + body + "\n```" : body;
70
+ return { title: "", markdown: markdown.slice(0, 20000) };
71
+ }
62
72
  const html = await response.text();
63
73
  const deps = loadReadableContentDependencies();
64
74
  const dom = new deps.JSDOM(html, { url });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bacnh85/pi-web",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Pi extension for web search, page extraction, Firecrawl scraping/crawling, and Crawl4AI headless browser crawling.",
5
5
  "type": "module",
6
6
  "license": "MIT",