feat(web_search): truncate results in TUI view, expand with ctrl+o

Add renderResult showing a collapsed preview (count + first 3 results)
until expanded via Ctrl+O. The full markdown still goes to the model via
content, so only the display is affected.
This commit is contained in:
2026-07-26 15:36:38 -04:00
parent 072701a649
commit 988b719eaa
+36 -1
View File
@@ -1,7 +1,12 @@
// Pi-Web Extension - Registers `web_search` and `web_fetch` tools backed by // Pi-Web Extension - Registers `web_search` and `web_fetch` tools backed by
// a shared headless Firefox session. Provider config lives in // a shared headless Firefox session. Provider config lives in
// ~/.pi/pi-web/config.json (env overrides supported). // ~/.pi/pi-web/config.json (env overrides supported).
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; import {
keyHint,
type AgentToolResult,
type ExtensionAPI,
type Theme,
} from "@mariozechner/pi-coding-agent";
import { Text } from "@mariozechner/pi-tui"; import { Text } from "@mariozechner/pi-tui";
import { Type } from "typebox"; import { Type } from "typebox";
import { ConfigError, loadConfig, resolveSettings } from "./src/config.ts"; import { ConfigError, loadConfig, resolveSettings } from "./src/config.ts";
@@ -10,6 +15,9 @@ import { searchKagi } from "./src/providers/kagi.ts";
import { searchSearxng } from "./src/providers/searxng.ts"; import { searchSearxng } from "./src/providers/searxng.ts";
import { SearchError, type SearchResult } from "./src/types.ts"; import { SearchError, type SearchResult } from "./src/types.ts";
// Collapsed-view preview cap. The full list still goes to the model via `content`.
const PREVIEW_RESULTS = 3;
function formatResults(results: SearchResult[]): string { function formatResults(results: SearchResult[]): string {
if (results.length === 0) return "(no results)"; if (results.length === 0) return "(no results)";
return results return results
@@ -17,6 +25,30 @@ function formatResults(results: SearchResult[]): string {
.join("\n\n"); .join("\n\n");
} }
function renderSearchResult(
result: AgentToolResult<unknown>,
expanded: boolean,
theme: Theme,
): Text {
const fullText = result.content[0]?.type === "text" ? result.content[0].text : "";
const raw = (result.details as { raw?: SearchResult[] } | undefined)?.raw;
// Expanded or unavailable details: show the full markdown the model received.
if (expanded || !raw || raw.length === 0) {
return new Text(fullText, 0, 0);
}
let text = theme.fg("muted", `${raw.length} result(s):`);
for (const r of raw.slice(0, PREVIEW_RESULTS)) {
text += `\n${theme.fg("toolTitle", r.title)} ${theme.fg("dim", r.url)}`;
}
const more = raw.length - PREVIEW_RESULTS;
if (more > 0) {
text += `\n${theme.fg("dim", `... ${more} more (${keyHint("app.tools.expand", "to expand")})`)}`;
}
return new Text(text, 0, 0);
}
function formatFetch(r: FetchResult): string { function formatFetch(r: FetchResult): string {
const header = const header =
r.title && r.title.trim() r.title && r.title.trim()
@@ -55,6 +87,9 @@ export default function (pi: ExtensionAPI) {
text.setText(theme.fg("toolTitle", theme.bold(`web_search "${display}"`))); text.setText(theme.fg("toolTitle", theme.bold(`web_search "${display}"`)));
return text; return text;
}, },
renderResult(result, { expanded }, theme) {
return renderSearchResult(result, expanded, theme);
},
async execute(_toolCallId, params) { async execute(_toolCallId, params) {
try { try {
const results = await runSearch(params.query); const results = await runSearch(params.query);