@browserwright/pi 0.11.0 → 0.13.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/README.md +19 -12
- package/core/chain.ts +1 -1
- package/core/config.ts +3 -3
- package/core/exec-module.ts +1 -1
- package/core/format.ts +2 -2
- package/index.ts +20 -14
- package/package.json +2 -2
- package/probe-run.ts +1 -1
- package/providers/browserwright-search.ts +1 -1
- package/verify.ts +2 -2
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@ 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
|
-
|
|
8
|
-
|
|
7
|
+
bw_web_fetch(url, provider?) → the page as Markdown
|
|
8
|
+
bw_web_search(query, provider?) → ranked links + the SERP features Google showed
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Both run through the user's **own Chrome**, so they see what the user sees —
|
|
@@ -29,14 +29,18 @@ browserwright version check # expect drift=equal
|
|
|
29
29
|
The npm package and the Python package are cut from the same git tag, so their
|
|
30
30
|
versions always match. Install them together.
|
|
31
31
|
|
|
32
|
+
The names are `bw_`-prefixed rather than bare `web_fetch`/`web_search` because
|
|
33
|
+
providers reserve generic tool names: grok rejects a custom function named
|
|
34
|
+
`web_search` with a 400, so the prefix keeps every provider safe.
|
|
35
|
+
|
|
32
36
|
## The two tools
|
|
33
37
|
|
|
34
|
-
`
|
|
35
|
-
`
|
|
38
|
+
`bw_web_search` returns **links, never page bodies**. The model then calls
|
|
39
|
+
`bw_web_fetch` on the one or two worth reading. That split is deliberate: fetching
|
|
36
40
|
all ten hits costs ~30 seconds and 50KB+ of context to answer a question that
|
|
37
41
|
usually needs one of them.
|
|
38
42
|
|
|
39
|
-
### What `
|
|
43
|
+
### What `bw_web_search` returns
|
|
40
44
|
|
|
41
45
|
| field | contents |
|
|
42
46
|
|-------|----------|
|
|
@@ -106,11 +110,11 @@ This package ships **only the browserwright rungs**. There is one per tool:
|
|
|
106
110
|
|
|
107
111
|
| tool | provider | kind |
|
|
108
112
|
|------|----------|------|
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
113
|
+
| `bw_web_fetch` | `browserwright` | `command` — `browserwright markdown <url>` |
|
|
114
|
+
| `bw_web_search` | `browserwright-search` | `module` — a session lifecycle in TS |
|
|
111
115
|
|
|
112
116
|
That is a real trade-off, and it points the wrong way for casual fetches: every
|
|
113
|
-
`
|
|
117
|
+
`bw_web_fetch` opens a tab in the daily browser and takes ~4-7s, where a hosted
|
|
114
118
|
reader API answers in ~1s without touching Chrome. What you get for it is login
|
|
115
119
|
state and full JS rendering, which no anonymous rung has.
|
|
116
120
|
|
|
@@ -247,11 +251,14 @@ worth repeating.
|
|
|
247
251
|
## Probe: rules from evidence, not guesses
|
|
248
252
|
|
|
249
253
|
```
|
|
250
|
-
/
|
|
251
|
-
/
|
|
252
|
-
/
|
|
254
|
+
/bw list # show both chains
|
|
255
|
+
/bw probe # every fetch provider
|
|
256
|
+
/bw probe browserwright
|
|
253
257
|
```
|
|
254
258
|
|
|
259
|
+
The slash command is `/bw` (not `/browserwright`) so it cannot be confused with
|
|
260
|
+
pi's `browserwright` skill, which pi exposes as `/skill:browserwright`.
|
|
261
|
+
|
|
255
262
|
Runs a provider against the real URLs in `probe-cases.json` — a normal article,
|
|
256
263
|
a client-rendered shell, a bot wall, a login wall, a 404, a page past the
|
|
257
264
|
truncation limit, a PDF, and localhost — then prints what came back and writes
|
|
@@ -304,7 +311,7 @@ the `tool_result` event see `isError: false`. So a chain failure here throws.
|
|
|
304
311
|
attacker, and blocking private addresses would remove localhost fetching,
|
|
305
312
|
which is a real workflow. The requested URL is printed so internal fetches
|
|
306
313
|
stay visible in the transcript.
|
|
307
|
-
- **No body fetching inside `
|
|
314
|
+
- **No body fetching inside `bw_web_search`.** See the two-tool split above.
|
|
308
315
|
|
|
309
316
|
## License
|
|
310
317
|
|
package/core/chain.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* The engine is payload-agnostic: it never looks inside `content`, it only asks
|
|
10
10
|
* the caller-supplied `inspect` to reduce it to text plus an item count. That is
|
|
11
|
-
* what lets one chain serve `
|
|
11
|
+
* what lets one chain serve `bw_web_fetch` (a Markdown blob) and `bw_web_search`
|
|
12
12
|
* (a list of results).
|
|
13
13
|
*/
|
|
14
14
|
|
package/core/config.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provider declarations are plain JSON files in providers/. Adding a provider
|
|
5
5
|
* means dropping one file in there — no code change, no registration table.
|
|
6
|
-
* A declaration that names no `role` serves
|
|
7
|
-
* majority of reader APIs are.
|
|
6
|
+
* A declaration that names no `role` serves the fetch role (`bw_web_fetch`),
|
|
7
|
+
* which is what the majority of reader APIs are.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
@@ -30,7 +30,7 @@ const DEFAULT_CONFIG: PiConfig = {
|
|
|
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 `/
|
|
33
|
+
// come from `/bw probe` evidence, not from guesses.
|
|
34
34
|
defaultFailWhen: {
|
|
35
35
|
minChars: 0,
|
|
36
36
|
minResults: 0,
|
package/core/exec-module.ts
CHANGED
|
@@ -25,7 +25,7 @@ import type { ModuleContext, ModuleProvider, ModuleRunner, ProviderOutcome, Role
|
|
|
25
25
|
/**
|
|
26
26
|
* Completed and in-flight loads, keyed by spec.
|
|
27
27
|
*
|
|
28
|
-
* This stores the PROMISE, not the resolved runner, on purpose. Two
|
|
28
|
+
* This stores the PROMISE, not the resolved runner, on purpose. Two bw_web_search
|
|
29
29
|
* calls fired in the same turn both miss a value cache and both import; under
|
|
30
30
|
* pi's jiti runtime that race is not merely wasteful but wrong — the second
|
|
31
31
|
* caller can observe a half-initialized module record and fail the load (see
|
package/core/format.ts
CHANGED
|
@@ -117,7 +117,7 @@ const ANSWER_BOX_CAP = 1200;
|
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
119
|
* The search success path. Links plus whatever SERP features the query
|
|
120
|
-
* triggered — but never page bodies: `
|
|
120
|
+
* triggered — but never page bodies: `bw_web_fetch` already does that, and the
|
|
121
121
|
* model is in a better position to decide which two of ten links are worth the
|
|
122
122
|
* tokens.
|
|
123
123
|
*
|
|
@@ -171,7 +171,7 @@ export function renderResults(result: ChainResult<SearchPayload>, query: string)
|
|
|
171
171
|
out.push("## Related searches", payload.relatedSearches.join(" · "), "");
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
out.push("Use
|
|
174
|
+
out.push("Use bw_web_fetch on a URL above to read it.");
|
|
175
175
|
return out.join("\n");
|
|
176
176
|
}
|
|
177
177
|
|
package/index.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @browserwright/pi — `
|
|
2
|
+
* @browserwright/pi — `bw_web_fetch` and `bw_web_search` for pi, backed by
|
|
3
3
|
* declarative providers that drive browserwright.
|
|
4
4
|
*
|
|
5
|
+
* Tool names are `bw_`-prefixed (not bare `web_fetch`/`web_search`) because
|
|
6
|
+
* providers reserve generic tool names: grok rejects a custom function named
|
|
7
|
+
* `web_search` with a 400. The prefix keeps every provider safe.
|
|
8
|
+
*
|
|
5
9
|
* A provider is a JSON file in providers/; adding one needs no code change.
|
|
6
10
|
* This package ships only the browserwright rungs, which are the ones that
|
|
7
11
|
* carry the user's login state. Drop your own JSON in to add a cheaper or
|
|
@@ -48,10 +52,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
48
52
|
onUpdate?.({ content: [{ type: "text", text: `*${text}*` }] });
|
|
49
53
|
};
|
|
50
54
|
|
|
51
|
-
// ----
|
|
55
|
+
// ---- bw_web_fetch ------------------------------------------------------
|
|
52
56
|
|
|
53
57
|
pi.registerTool({
|
|
54
|
-
name: "
|
|
58
|
+
name: "bw_web_fetch",
|
|
55
59
|
label: "Fetch Web Page",
|
|
56
60
|
description:
|
|
57
61
|
"Fetch a URL and return its content as Markdown. " +
|
|
@@ -60,7 +64,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
60
64
|
"Output over 50KB is truncated and the full text written to a temp file whose path is given.",
|
|
61
65
|
promptSnippet: "Fetch a URL as markdown, through the user's real browser",
|
|
62
66
|
promptGuidelines: [
|
|
63
|
-
"Prefer `
|
|
67
|
+
"Prefer `bw_web_fetch` over curl or a shell HTTP client for reading web pages — it renders JavaScript " +
|
|
64
68
|
"and carries the user's login state, so it can read pages an anonymous request cannot.",
|
|
65
69
|
],
|
|
66
70
|
parameters: Type.Object({
|
|
@@ -92,7 +96,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
92
96
|
if (ctx.hasUI) ctx.ui.setStatus("browserwright", "");
|
|
93
97
|
|
|
94
98
|
if (!result.ok) {
|
|
95
|
-
throw new ToolFailure(renderFailure(result, url, { tool: "
|
|
99
|
+
throw new ToolFailure(renderFailure(result, url, { tool: "bw_web_fetch", alternatives: fetchNames }));
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
return {
|
|
@@ -113,20 +117,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
113
117
|
},
|
|
114
118
|
});
|
|
115
119
|
|
|
116
|
-
// ----
|
|
120
|
+
// ---- bw_web_search -----------------------------------------------------
|
|
117
121
|
|
|
118
122
|
pi.registerTool({
|
|
119
|
-
name: "
|
|
123
|
+
name: "bw_web_search",
|
|
120
124
|
label: "Search the Web",
|
|
121
125
|
description:
|
|
122
126
|
"Search the web and return ranked results as title, URL, snippet and date. " +
|
|
123
127
|
`Providers in order: ${config.order.search.join(" → ")}. ` +
|
|
124
128
|
"Also returns the engine's own AI Overview, knowledge panel, 'people also ask' and " +
|
|
125
129
|
"related searches when that query triggered them. Returns links, never page bodies — " +
|
|
126
|
-
"call
|
|
130
|
+
"call bw_web_fetch on the ones worth reading.",
|
|
127
131
|
promptSnippet: "Search the web and get back ranked links",
|
|
128
132
|
promptGuidelines: [
|
|
129
|
-
"`
|
|
133
|
+
"`bw_web_search` returns links, not page contents. After searching, call `bw_web_fetch` on the one or two " +
|
|
130
134
|
"results actually worth reading rather than fetching all of them.",
|
|
131
135
|
],
|
|
132
136
|
parameters: Type.Object({
|
|
@@ -141,7 +145,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
141
145
|
}),
|
|
142
146
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
143
147
|
const query = params.query.trim();
|
|
144
|
-
if (!query) throw new ToolFailure("
|
|
148
|
+
if (!query) throw new ToolFailure("bw_web_search needs a non-empty query");
|
|
145
149
|
const setStatus = statusReporter(ctx, onUpdate);
|
|
146
150
|
|
|
147
151
|
const result = await runChain<SearchPayload>({
|
|
@@ -167,7 +171,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
167
171
|
if (ctx.hasUI) ctx.ui.setStatus("browserwright", "");
|
|
168
172
|
|
|
169
173
|
if (!result.ok) {
|
|
170
|
-
throw new ToolFailure(renderFailure(result, query, { tool: "
|
|
174
|
+
throw new ToolFailure(renderFailure(result, query, { tool: "bw_web_search", alternatives: searchNames }));
|
|
171
175
|
}
|
|
172
176
|
|
|
173
177
|
return {
|
|
@@ -188,11 +192,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
188
192
|
},
|
|
189
193
|
});
|
|
190
194
|
|
|
191
|
-
// ---- /
|
|
195
|
+
// ---- /bw ---------------------------------------------------------------
|
|
196
|
+
// Named `/bw` (not `/browserwright`) so it cannot be confused with the
|
|
197
|
+
// `browserwright` skill, which pi exposes as `/skill:browserwright`.
|
|
192
198
|
|
|
193
|
-
pi.registerCommand("
|
|
199
|
+
pi.registerCommand("bw", {
|
|
194
200
|
description:
|
|
195
|
-
"Inspect providers (/
|
|
201
|
+
"Inspect providers (/bw list) or probe one against real URLs (/bw probe <provider>)",
|
|
196
202
|
handler: async (args, ctx) => {
|
|
197
203
|
const [subcommand, target] = args.trim().split(/\s+/);
|
|
198
204
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@browserwright/pi",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.13.0",
|
|
4
|
+
"description": "bw_web_fetch and bw_web_search for the pi coding agent, driving the user's real browser through browserwright",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"keywords": [
|
package/probe-run.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Headless probe runner — the same code path as `/
|
|
2
|
+
* Headless probe runner — the same code path as `/bw probe`, without pi's
|
|
3
3
|
* confirmation dialog. Use it when you want the evidence matrix captured to a
|
|
4
4
|
* file rather than rendered into a session.
|
|
5
5
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `
|
|
2
|
+
* The `bw_web_search` rung: drive a real search engine in the user's own Chrome.
|
|
3
3
|
*
|
|
4
4
|
* This is a `kind: "module"` provider rather than a `kind: "command"` one
|
|
5
5
|
* because a search is not one shot at a subprocess. It is: mint a session,
|
package/verify.ts
CHANGED
|
@@ -41,7 +41,7 @@ if (url !== undefined) {
|
|
|
41
41
|
console.log(
|
|
42
42
|
chained.ok
|
|
43
43
|
? `${renderSuccess(chained, { url, maxBytes: config.maxBytes, maxLines: config.maxLines }).slice(0, 600)}\n`
|
|
44
|
-
: `${renderFailure(chained, url, { tool: "
|
|
44
|
+
: `${renderFailure(chained, url, { tool: "bw_web_fetch", alternatives: [...providersForRole(providers, "fetch").keys()] })}\n`,
|
|
45
45
|
);
|
|
46
46
|
|
|
47
47
|
console.log("── each fetch provider in isolation ──");
|
|
@@ -84,7 +84,7 @@ if (query !== undefined) {
|
|
|
84
84
|
searched.ok
|
|
85
85
|
? renderResults(searched, query)
|
|
86
86
|
: renderFailure(searched, query, {
|
|
87
|
-
tool: "
|
|
87
|
+
tool: "bw_web_search",
|
|
88
88
|
alternatives: [...providersForRole(providers, "search").keys()],
|
|
89
89
|
}),
|
|
90
90
|
);
|