fix(tool-web): unexport renderFetchOutput so its memo stays behind the frozen path

renderFetchOutput has no external consumer: only formatFetchOutput and
fetchMetaFromValue call it, both through the registry, which deep-freezes
the result value. Exporting it let a hypothetical caller mutate a cached
input or the returned RenderedFetch and desync the card's truncated flag
from the model text. Drop it from the barrel and document that the memo
needs no defensive copy because every caller is internal and read-only.
This commit is contained in:
Chinesezjc
2026-07-30 21:28:54 +08:00
parent 47219dd49f
commit fb0063de48
2 changed files with 10 additions and 6 deletions

View File

@@ -266,10 +266,14 @@ interface RenderedFetch {
* limits the source prefix processed synchronously, then applies again where the
* complete output — header, rendered body, and footer — is known.
*
* The tool registry calls this once through `output.render` and again through
* `output.presentationMeta`, both with the same frozen result value; the
* conversion is memoized per `(result, maxOutputChars)` so the synchronous DOM
* parse and turndown walk run once, not twice, on the same body.
* Package-internal: the only callers are {@link formatFetchOutput} and
* {@link fetchMetaFromValue}, both reached through the tool registry, which
* deep-freezes the result value before calling `output.render` and
* `output.presentationMeta`. The conversion is memoized per
* `(result, maxOutputChars)` so the synchronous DOM parse and turndown walk run
* once, not twice, on that same frozen value. Keeping it unexported means no
* caller can mutate a cached input or the returned {@link RenderedFetch}, so the
* memo needs no defensive copy.
*
* @param result - the seam's fetch outcome.
* @param maxOutputChars - cap on the complete returned string; a cut body gets
@@ -277,7 +281,7 @@ interface RenderedFetch {
* @returns the complete `Fetched <url> (HTTP <status>)`-headed text and whether
* the provider, a source cut, or the cap trimmed the content.
*/
export function renderFetchOutput(result: WebFetchResult, maxOutputChars: number): RenderedFetch {
function renderFetchOutput(result: WebFetchResult, maxOutputChars: number): RenderedFetch {
const byCap = renderCache.get(result) ?? new Map<number, RenderedFetch>()
const cached = byCap.get(maxOutputChars)
if (cached !== undefined) return cached

View File

@@ -14,7 +14,7 @@ import { applyWebFetchTool } from './fetch.ts'
export { WEB_SEARCH_MAX_RESULTS, applyWebSearchTool, formatSearchOutput, parseSearchArgs, presentSearchCall, presentSearchResult, searchMetaFromValue, searchMetaFromResult } from './search.ts'
export type { WebSearchMeta } from './search.ts'
export { applyWebFetchTool, formatFetchOutput, renderFetchOutput, parseFetchArgs, presentFetchCall, presentFetchResult, fetchMetaFromValue, fetchMetaFromResult } from './fetch.ts'
export { applyWebFetchTool, formatFetchOutput, parseFetchArgs, presentFetchCall, presentFetchResult, fetchMetaFromValue, fetchMetaFromResult } from './fetch.ts'
export type { WebFetchMeta } from './fetch.ts'
/** Cordis plugin name used by loader diagnostics. */