import { batchProgressFromCrawlPages, isBatchProgress, isBatchProgressView, } from "../../batch/progress-state.ts"; import type { CrawlRunResult } from "../../crawl/runner.ts"; import { formatCrawlStrategyLabel } from "../../crawl/state.ts"; import { isProgress, type PiToolShell, type ToolContext } from "../../types.ts"; import { toolBatchProgress, toolBatchResult } from "../tool-batch.ts"; import { toolContextPackageResponseId, toolErrorLabel, toolExpandHint, toolSessionNotice, } from "../tool-labels.ts"; import { toolProgressView, toolProgressLayout } from "../tool-progress.ts"; import { renderResourceItemList as toolResourceList } from "../tool-resource.ts"; import { toolResultTree } from "../tool-result-tree.ts"; import { buildToolResultDetails } from "../tool-result.ts"; import { countSegments as count, toolStatus } from "../tool-status.ts"; import { muted as toolNeutral } from "../tui.ts"; import type { RenderComponent, RenderTheme } from "../types.ts"; export type CrawlPageView = Partial; export function crawlExpandedDetails( pages: readonly CrawlPageView[], metadata: { jobId?: unknown; packageResponseId?: unknown } = {}, ): string { const cap = Math.max(100, Math.min(500, Math.floor(1000 / Math.max(1, pages.length)))); return toolResourceList( pages.map((page) => { const url = page.finalUrl ?? page.url ?? "unknown URL"; if (page.error) return { ok: false, url, fields: {}, error: page.error }; const excerpt = [page.data?.description, page.data?.markdown, page.data?.text].find(Boolean); return { ok: true, url, finalUrl: page.finalUrl && page.url && page.finalUrl !== page.url ? page.finalUrl : undefined, title: page.data?.title, excerpt: excerpt ? excerpt.replaceAll(/\s+/gu, " ").trim().slice(0, cap) : undefined, fields: { status: page.status, mode: page.mode, format: page.format, contentType: page.contentType, downloadedBytes: page.downloadedBytes, durationMs: page.timing?.durationMs, cached: page.cache?.cached, staleness: page.cache?.staleness, truncated: page.truncated, }, }; }), { header: "Per-page details:", metadata }, ); } export function renderWebCrawlLookupResult( result: PiToolShell, expanded = false, theme?: RenderTheme, ): RenderComponent { const envelope = result.details as Partial>; return toolProgressLayout( { body: envelope.summary ?? result.content[0].text, expanded, expandedSections: (width) => [ toolResultTree(buildToolResultDetails(envelope as Record), width, theme), ], responseId: envelope.responseId, padToWidth: true, }, theme, ); } export function renderWebCrawlResult( result: PiToolShell, expanded = false, theme?: RenderTheme, ): RenderComponent { const envelope = result.details as Partial>>; if (isProgress(envelope)) { if (isBatchProgress(envelope)) return toolBatchProgress(envelope, expanded, theme); return toolProgressView("web_crawl", envelope, theme, { allowIcons: true }); } const data = envelope.data; const metadata = data?.metadata; const strategy = metadata?.strategy; const summary = envelope.error ? toolErrorLabel("web_crawl", envelope.error, { allowIcons: true }) : toolStatus( [ count.success(metadata?.succeededCount ?? 0, "succeeded", theme), count.failure(metadata?.failedCount ?? 0, "failed", theme), count.activity(metadata?.visitedCount ?? 0, "visited", "◉", theme), toolNeutral(`→ frontier ${metadata?.frontierCount ?? 0}`, theme), strategy && toolNeutral(`· ${formatCrawlStrategyLabel(strategy) ?? strategy} crawl`, theme), !expanded && toolExpandHint, ], theme, ); const pages = Array.isArray(data?.pages) ? (data.pages as CrawlPageView[]) : []; const progress = isBatchProgressView(envelope.diagnostics?.batchProgress) ? envelope.diagnostics.batchProgress : batchProgressFromCrawlPages(pages); return toolBatchResult( { progress, summary, notice: toolSessionNotice(envelope), preview: crawlExpandedDetails(pages, { jobId: envelope.diagnostics?.jobId, packageResponseId: toolContextPackageResponseId(envelope), }), responseId: envelope.responseId, }, expanded, theme, ); }