Introduce web access as a first-class capability seam so the model-facing web tools stay stable while backends change. dsh-web owns ctx.web as a provider registry with registration-order-independent selection and the WebError taxonomy; dsh-web-search-exa, dsh-web-search-perplexity, and dsh-web-fetch-local register capabilities into it; dsh-tool-web is the sole owner of the model-facing web_search/web_fetch schemas, prompt sections, and HTML-to-markdown presentation. Search and fetch are deliberately one seam. Providers ship as namespace plugins that register into ctx.web (like an LlmAdapter into ctx.llm), not key-owning services, since multiple search providers cannot each own the key. Tool registration follows product enablement, not backend availability, so load order/credentials never enter the model contract; the seam resolves the provider at execution time and surfaces a structured WebError otherwise. Moves the RFC to implemented/ amended to match what shipped. Example/app configs are intentionally not wired yet (RFC migration step 6).
35 lines
2.6 KiB
Markdown
35 lines
2.6 KiB
Markdown
# @deepseek-ai/dsh-web-fetch-local
|
|
|
|
An anonymous public HTTP(S) `WebFetchProvider` for the harness [web capability seam](../web/README.md) (`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:` and `https:` 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. |
|
|
| `userAgent` | `deepseek-harness/…` | `User-Agent` header. |
|
|
|
|
## 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](../../../docs/rfc/implemented/architecture/2026-06-24-web-capability-seam.md). Until it lands, this provider is an SSRF primitive and **must not be enabled** in a deployment that can reach sensitive internal network targets.
|