WebService exposed an observation surface nothing in production observes: the web/providers-change event (declared, emitted on every provider registration/disposal, rollback-yield ordered before the emit solely so a throwing change listener unwinds the registration) and the aggregated searchStatus()/fetchStatus() query with its WebCapabilityStatus union. dsh-tool-web executes through ctx.web.search()/fetch() and routes on the structured WebError codes selection throws at execution time; tool registration follows product enablement, not provider availability. The only listeners/callers were the web packages' own tests, and the tool-web README / architecture.md prose claiming the tool 'reads only the aggregated searchStatus()/fetchStatus()' had drifted from the call sites. Remove the event declaration, both emits, and the rollback-before-emit machinery (the plain ctx.effect disposer keeps HMR cleanup, matching LlmService.registerAdapter). Remove searchStatus()/fetchStatus(), resolveStatus(), and WebCapabilityStatus; the provider-private status() stays as the execution-time selection input. Delete the listener-throw rollback test, and rewrite every event/status assertion across the web packages' tests onto caller-observable behavior: a successful search()/fetch() or the structured WEB_PROVIDER_* codes. Regenerate the cordis catalog; update the web/tool-web READMEs, the architecture.md web paragraph, core-data-structures/web.md, and the type-equiv manifest; amend the web capability seam RFC's facts to the shipped surface. This follows the llm/adapter-change precedent: a boot-time backend-registry signal and an availability probe distinct from executing both sit on the cut side of its keep/cut criterion. RFC: docs/rfc/implemented/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md
@deepseek-ai/dsh-web-fetch-local
An anonymous public HTTP(S) WebFetchProvider for the harness web capability seam (ctx.web). It retrieves a concrete URL and returns a status code plus bounded decoded content.
This is an implementation package: it registers a provider into ctx.web, it does not own the key and it does not register a model-facing tool. It is a function/namespace plugin (inject: ['web']).
Responsibility split
The provider owns safe resource retrieval: URL validation, HTTP transport, redirect policy, timeout, abort propagation, byte caps, charset decoding, content-type classification, and binary rejection. @deepseek-ai/dsh-tool-web owns presentation (HTML→markdown, truncation formatting). A non-2xx HTTP response is a result (status code + decoded body), not an error; WebError is reserved for failures to safely retrieve or represent the resource.
Transport hygiene
- Accepts only
http:andhttps:URLs; rejects credentials in URLs (WEB_BLOCKED_URL) and over-long/malformed URLs (WEB_INVALID_URL). - Enforces a max URL length, response byte cap (
WEB_FETCH_TOO_LARGE), decoded body character cap, timeout (WEB_FETCH_TIMEOUT), and redirect hop cap. - Propagates the caller's abort signal (
WEB_ABORTED) into the network request and the streaming read. - Follows only same-origin redirects; a cross-origin redirect fails with
WEB_REDIRECT_BLOCKED, requiring a fresh tool call (the model of Claude Code's WebFetch). - Sends an explicit product
User-Agent, never a browser disguise. - Rejects unsupported (e.g. binary) content types with
WEB_UNSUPPORTED_CONTENT_TYPE.
Config
| Key | Default | Meaning |
|---|---|---|
maxUrlLength |
2048 |
Maximum accepted request URL length. |
maxResponseBytes |
5_000_000 |
Maximum response body size in bytes. |
maxBodyChars |
100_000 |
Maximum decoded body length in characters. |
timeoutMs |
30_000 |
Default fetch timeout. |
maxTimeoutMs |
120_000 |
Upper bound for a per-request timeout override. |
maxRedirects |
5 |
Maximum same-origin redirect hops (0 follows none). |
userAgent |
deepseek-harness/… |
User-Agent header. |
The numeric limits are validated at plugin construction: every cap except maxRedirects must be a positive finite number, and maxRedirects must be a non-negative integer. An invalid value throws rather than silently constructing a provider with nonsensical limits.
Security note
SSRF / private-network protection (blocking private, loopback, link-local, multicast, and otherwise non-public destinations, with DNS-resolve-then-validate and per-hop re-validation) is deferred — see the web capability seam RFC. Until it lands, this provider is an SSRF primitive and must not be enabled in a deployment that can reach sensitive internal network targets.