response+fetch: WebSearch parity via parseWebSearch normalizer (issue 009)
WebSearch results were scanned unevenly across hosts: Claude flattened the
structured results object with JSON.stringify (injecting `","url":"` punctuation
that splits field-boundary payloads and erases per-result structure), while
OpenCode scanned the flat Exa string. WebFetch already gets clean text.
Add parseWebSearch(response) in `response`: a zod schema (safeParse, per
analyze-with-llm's idiom) recognizes the WebSearch shape (results array, or
`{results}`/`{content}` wrapper), then collects every string in its results —
at any depth and across both `results` and `content` — and newline-joins them.
Unrecognized shapes fall back to JSON.stringify, so an unknown shape is never
fail-open (still scanned, as a blob). Deep collection means a payload nested in
a result sub-field (e.g. Exa `highlights`) isn't missed, and a results array
mixing in a non-result element is still scanned as text, not a JSON blob.
Both host adapters route WebSearch results through it before detect/neutralize
— no new detector. Scope: structure-aware scanning in, exfil-URL parsing
deferred (no field tool does it; not in the WebFetch path; outbound exfil is
already covered by blockCanaryExfil).
parseWebSearch subsumes toolResponseText (string->identity, object->stringify),
so toolResponseText and its test are removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>