aboutsummaryrefslogtreecommitdiff
path: root/castle-search/src/format.ts
diff options
context:
space:
mode:
Diffstat (limited to 'castle-search/src/format.ts')
-rw-r--r--castle-search/src/format.ts54
1 files changed, 54 insertions, 0 deletions
diff --git a/castle-search/src/format.ts b/castle-search/src/format.ts
new file mode 100644
index 0000000..aef6c3b
--- /dev/null
+++ b/castle-search/src/format.ts
@@ -0,0 +1,54 @@
+/**
+ * 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;
+ /** Engine display name, e.g. "DuckDuckGo". */
+ engine: string;
+ /** Where the engine choice came from, e.g. "/search-engine". */
+ engineSource: string;
+ results: SearchResult[];
+};
+
+export const formatResults = (input: FormatInput): string => {
+ const lines: string[] = [
+ // Naming the engine on every result set matters more than it looks: the
+ // agent may have switched engines mid-conversation to get around a block,
+ // and results from different indexes are not interchangeable evidence.
+ `Search results for "${input.query}" — ${input.results.length} hits from ${input.engine} ` +
+ `(${input.engineSource}), 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.]`;