@browserwright/pi 0.18.1 → 0.18.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/README.md CHANGED
@@ -4,12 +4,14 @@ Two tools for [pi](https://github.com/badlogic/pi-mono), backed by **declarative
4
4
  providers** that drive [browserwright](https://github.com/broven/browserwright):
5
5
 
6
6
  ```
7
- bw_web_fetch(url, provider?) → the page as Markdown
7
+ bw_web_fetch(url, provider?) → the page as Markdown, or raw text for text endpoints
8
8
  bw_web_search(query, provider?) → ranked links + the SERP features Google showed
9
9
  ```
10
10
 
11
- Both run through the user's **own Chrome**, so they see what the user sees —
12
- including pages behind a login. Zero npm dependencies; `typebox` and the pi
11
+ The browserwright paths run through the user's **own Chrome**, so they see what
12
+ the user sees — including pages behind a login. Fetch also has a raw-text
13
+ fallback for endpoints such as GitHub Raw; that fallback makes a direct request
14
+ and returns the text body verbatim. Zero npm dependencies; `typebox` and the pi
13
15
  packages come from pi's own install.
14
16
 
15
17
  ## Install
@@ -106,17 +108,20 @@ truncated: 382 of 480 lines (49.7KB of 71.5KB) · full: /tmp/browserwright-pi-xx
106
108
 
107
109
  ## What ships, and what does not
108
110
 
109
- This package ships **only the browserwright rungs**. There is one per tool:
111
+ This package ships the browserwright-backed rungs plus a text fallback for fetch.
112
+ There is one browserwright rung per tool:
110
113
 
111
114
  | tool | provider | kind |
112
115
  |------|----------|------|
113
- | `bw_web_fetch` | `browserwright` | `command` — `browserwright markdown <url>` |
116
+ | `bw_web_fetch` | `browserwright` → `raw` | `command` — browser-rendered HTML, then `module` — text body verbatim |
114
117
  | `bw_web_search` | `browserwright-search` | `module` — a session lifecycle in TS |
115
118
 
116
- That is a real trade-off, and it points the wrong way for casual fetches: every
117
- `bw_web_fetch` opens a tab in the daily browser and takes ~4-7s, where a hosted
118
- reader API answers in ~1s without touching Chrome. What you get for it is login
119
- state and full JS rendering, which no anonymous rung has.
119
+ That is a real trade-off, and it points the wrong way for casual HTML fetches: a
120
+ browserwright `bw_web_fetch` opens a tab in the daily browser and takes ~4-7s,
121
+ where a hosted reader API answers in ~1s without touching Chrome. What you get
122
+ for the browser rung is login state and full JS rendering. Text endpoints such
123
+ as GitHub Raw skip the browser conversion failure and are returned verbatim by
124
+ the `raw` fallback.
120
125
 
121
126
  **The chain engine is still here.** Drop your own JSON into `providers/` to add a
122
127
  cheaper or anonymous rung ahead of the browser one — nothing needs to be
@@ -204,7 +209,8 @@ extraction has to run against the live DOM rather than the document response.
204
209
  ### `returns`
205
210
 
206
211
  `markdown` | `html` | `text` | `results`. **The core never converts between
207
- them**; it only labels the output so the model knows what it is reading.
212
+ them**; it only labels the output so the model knows what it is reading. The
213
+ built-in `raw` fetch provider returns accepted text response bodies unchanged.
208
214
 
209
215
  ## failWhen: the reason the chain exists
210
216
 
package/config.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "order": {
3
- "fetch": ["browserwright"],
3
+ "fetch": ["browserwright", "raw"],
4
4
  "search": ["browserwright-search"]
5
5
  },
6
6
  "defaultFailWhen": {
package/core/config.ts CHANGED
@@ -24,13 +24,14 @@ const LOG_PREFIX = "[browserwright-pi]";
24
24
 
25
25
  const DEFAULT_CONFIG: PiConfig = {
26
26
  order: {
27
- fetch: ["browserwright"],
27
+ fetch: ["browserwright", "raw"],
28
28
  search: ["browserwright-search"],
29
29
  },
30
30
  // The default line of defence. minChars stays 0 on purpose: a false positive
31
31
  // escalates to a rung that opens a tab in the user's real Chrome, so
32
32
  // over-eager rejection interrupts them. Per-provider thresholds are meant to
33
- // come from `/bw probe` evidence, not from guesses.
33
+ // come from `/bw probe` evidence, not from guesses. The raw text rung opts out
34
+ // of the phrase list because source files may legitimately contain those words.
34
35
  defaultFailWhen: {
35
36
  minChars: 0,
36
37
  minResults: 0,
package/index.ts CHANGED
@@ -58,14 +58,14 @@ export default function (pi: ExtensionAPI) {
58
58
  name: "bw_web_fetch",
59
59
  label: "Fetch Web Page",
60
60
  description:
61
- "Fetch a URL and return its content as Markdown. " +
61
+ "Fetch a URL and return its content as Markdown or text. " +
62
62
  `Tries providers in order until one returns usable content: ${config.order.fetch.join(" → ")}. ` +
63
63
  "The response header states which provider answered and what format the body is in. " +
64
64
  "Output over 50KB is truncated and the full text written to a temp file whose path is given.",
65
- promptSnippet: "Fetch a URL as markdown, through the user's real browser",
65
+ promptSnippet: "Fetch a URL as Markdown or text, through the user's real browser",
66
66
  promptGuidelines: [
67
67
  "Prefer `bw_web_fetch` over curl or a shell HTTP client for reading web pages — it renders JavaScript " +
68
- "and carries the user's login state, so it can read pages an anonymous request cannot.",
68
+ "and carries the user's login state, while its text fallback also reads raw source endpoints.",
69
69
  ],
70
70
  parameters: Type.Object({
71
71
  url: Type.String({ description: "HTTP(S) URL to fetch" }),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@browserwright/pi",
3
- "version": "0.18.1",
4
- "description": "bw_web_fetch and bw_web_search for the pi coding agent, driving the user's real browser through browserwright",
3
+ "version": "0.18.2",
4
+ "description": "bw_web_fetch and bw_web_search for the pi coding agent, using browserwright plus a raw text fallback",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",
7
7
  "keywords": [
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The raw text fetch rung.
3
+ *
4
+ * `browserwright markdown` deliberately only converts HTML. Public source
5
+ * endpoints such as raw.githubusercontent.com return the source as text/plain,
6
+ * so they need a reader that returns the response body verbatim instead of
7
+ * trying to turn a browser's non-HTML document into Markdown.
8
+ */
9
+
10
+ import type { ModuleContext, ProviderOutcome } from "../core/types.ts";
11
+
12
+ /**
13
+ * MIME types whose response body is safe to hand back as text.
14
+ *
15
+ * `text/*` covers source files served as text/plain. The application types
16
+ * cover APIs and source/document formats that are commonly served without a
17
+ * text/* MIME type. Binary responses such as application/pdf and
18
+ * application/octet-stream are intentionally not decoded here.
19
+ */
20
+ const TEXT_APPLICATION_TYPES = new Set([
21
+ "application/graphql",
22
+ "application/javascript",
23
+ "application/json",
24
+ "application/ld+json",
25
+ "application/manifest+json",
26
+ "application/sql",
27
+ "application/toml",
28
+ "application/typescript",
29
+ "application/xml",
30
+ "application/x-javascript",
31
+ "application/x-yaml",
32
+ "application/yaml",
33
+ ]);
34
+
35
+ export function isTextContentType(raw: string | null | undefined): boolean {
36
+ const contentType = (raw ?? "").split(";", 1)[0].trim().toLowerCase();
37
+ return contentType.startsWith("text/") || TEXT_APPLICATION_TYPES.has(contentType);
38
+ }
39
+
40
+ export default async function rawText(
41
+ subject: string,
42
+ ctx: ModuleContext,
43
+ ): Promise<ProviderOutcome<string>> {
44
+ let response: Response;
45
+ try {
46
+ response = await fetch(subject, {
47
+ redirect: "follow",
48
+ signal: ctx.signal,
49
+ });
50
+ } catch (error) {
51
+ if (ctx.signal?.aborted) return { ok: false, reason: "aborted" };
52
+ return { ok: false, reason: `fetch failed: ${(error as Error).message}` };
53
+ }
54
+
55
+ const contentType = response.headers.get("content-type") ?? "";
56
+ if (!isTextContentType(contentType)) {
57
+ const type = contentType.split(";", 1)[0].trim() || "unknown";
58
+ return {
59
+ ok: false,
60
+ status: response.status,
61
+ reason: `not a text response (Content-Type: ${type})`,
62
+ };
63
+ }
64
+
65
+ let text: string;
66
+ try {
67
+ text = await response.text();
68
+ } catch (error) {
69
+ return { ok: false, status: response.status, reason: `could not read response: ${(error as Error).message}` };
70
+ }
71
+
72
+ if (!response.ok) {
73
+ const hint = text.slice(0, 200).replace(/\s+/g, " ").trim();
74
+ return { ok: false, status: response.status, reason: `http ${response.status}${hint ? `: ${hint}` : ""}` };
75
+ }
76
+
77
+ return { ok: true, content: text, status: response.status };
78
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "raw",
3
+ "role": "fetch",
4
+ "kind": "module",
5
+ "module": "./providers/raw-text.ts",
6
+ "returns": "text",
7
+ "timeoutMs": 30000,
8
+ "failWhen": {
9
+ "matches": []
10
+ },
11
+ "_note": [
12
+ "Returns text responses verbatim for endpoints such as raw.githubusercontent.com.",
13
+ "The browserwright provider remains first, so HTML gets browser rendering and the user's login state when available.",
14
+ "Binary responses are rejected rather than decoded as corrupt text."
15
+ ]
16
+ }