/** * Web Extension * * Key-free web access: web_search scrapes the DuckDuckGo HTML endpoint (no * API key, no account) and web_fetch retrieves a URL as readable text. * Honors the local-only setup: no cloud accounts, plain HTTPS to public web. */ import type { LookupAddress } from 'node:dns' import { lookup } from 'node:dns/promises' import type { LookupFunction } from 'node:net' import type { Usage } from '@earendil-works/pi-ai' import type { ExtensionAPI } from '@earendil-works/pi-coding-agent' import { Type } from 'typebox' import { htmlToMarkdown, removeTags } from './internal/html-markdown.ts' import { type CompletionRegistry, completeText } from './internal/model-complete.ts' import { capForContext } from './internal/output-guard.ts' import { parseNumericEnv } from './internal/values.ts' import { httpFetch } from './internal/web-transport.ts' const SEARCH_ENDPOINT = 'https://html.duckduckgo.com/html/?q=' const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) pi-code-web/0.1' const MAX_FETCH_CHARS = 30_000 /** How much of a body is read, and what happens past that. */ type BodyLimit = { maxBytes: number; overflow: 'cut' | 'refuse' } // A page is converted whole, then capped at MAX_FETCH_CHARS. Real pages put hundreds // of KB of markup ahead of their content (a GitHub pull request's conversation starts // past 250k chars), so the raw ceiling bounds memory only; the converter is linear // (10 MiB converts in ~120ms). Over it the fetch is refused, never cut: a cut can land // inside a