From 495de0d5283dd3e4a6ef715b596c4a2892e95915 Mon Sep 17 00:00:00 2001 From: Igor Soarez Date: Mon, 3 Aug 2026 21:18:02 +0100 Subject: Web search for pi through an existing Chrome over CDP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Attaches to a browser that is already running — never launches one — using the same endpoint configuration as pi-browser-harness, so a single /browser-target choice governs both packages. One tool, castle_cdp_search, deliberately not named web_search so it coexists with pi-web-access rather than shadowing it. Notes from validating against castle's Chrome: - tbs=qdr:*, the parameter Google's own Tools menu writes, renders an empty page on this profile; the older as_qdr=* works. Any date filter combined with udm=14 is also empty, so recency drops udm. - Target.createTarget must not be raced against the abort signal: raceAbort abandons the promise but cannot cancel the command, and the command's side effect is a tab nothing is left holding. - A search cancelled while queued has to be removed from the semaphore queue, or the slot handed to it later is never counted back. - A decaying rate-limit block stops serving /sorry/ and returns an empty results page instead, indistinguishable from a genuine zero-hit search. --- src/format.ts | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/format.ts (limited to 'src/format.ts') diff --git a/src/format.ts b/src/format.ts new file mode 100644 index 0000000..d3872a1 --- /dev/null +++ b/src/format.ts @@ -0,0 +1,46 @@ +/** + * Turning results into the text the model reads. + * + * Twenty results with 600-character snippets is roughly 15KB, comfortably under + * pi's 50KB / 2000-line budget — but the truncation is applied anyway rather + * than argued about, because a pathological page could blow past it and an + * overflowing tool result costs the whole session, not just this call. + */ + +import type { SearchResult } from "./extract.ts"; + +export type FormatInput = { + query: string; + searchUrl: string; + finalUrl: string; + endpoint: string; + results: SearchResult[]; +}; + +export const formatResults = (input: FormatInput): string => { + const lines: string[] = [ + `Search results for "${input.query}" (${input.results.length} hits, via Chrome at ${input.endpoint})`, + `Query URL: ${input.searchUrl}`, + ]; + if (input.finalUrl !== input.searchUrl) lines.push(`Landed on: ${input.finalUrl}`); + lines.push(""); + + input.results.forEach((result, index) => { + lines.push(`${index + 1}. ${result.title}`); + lines.push(` ${result.url}`); + if (result.snippet) lines.push(` ${result.snippet}`); + lines.push(""); + }); + + return lines.join("\n").trimEnd(); +}; + +/** + * The note appended when pi's truncation utilities cut the output. Says what was + * lost and what to do about it — a bare "[truncated]" tells the model nothing it + * can act on, and there is no temp file to point at because the full text only + * ever existed in memory. + */ +export const truncationNote = (shown: number, total: number, bytesShown: string, bytesTotal: string): string => + `\n\n[Output truncated: ${shown} of ${total} lines (${bytesShown} of ${bytesTotal}). ` + + `Re-run castle_cdp_search with a smaller numResults to see the rest.]`; -- cgit v1.3.1