Files
deepseek-harness/packages/web/web
Turtle 38c7a9fbf4 feat(release): publish package source, declaration maps, and documentation (#5884)
* feat(release): publish package source, declaration maps, and documentation

Every release member publishes its TypeScript source and the declaration
maps that resolve into it, and @deepseek-ai/dsh publishes the repository
Markdown documentation staged from docs/ at prepack time.

The ./src/* export most members already declared now resolves for an
installed consumer, and a consumer editor follows a declaration to the
implementation. Bundled JavaScript maps stay unpublished; the vendored
family and the Desktop runtime file policy are unchanged.

Rationale: .agents/notes/implemented/process/2026-10-08-published-source-and-docs.md

* feat(release): publish JavaScript maps and compress bundles

Every emitted JavaScript map ships beside the bundle it describes, and the
workspace build and the client preset compress bundles without mangling
identifiers.

The documentation payload now mirrors the website publication manifest and
is removed again by `postpack`; Desktop and the Python executable exclude
the published source, documentation, and maps; `clean` removes a staged
copy. Typert's publication contract gains the Remote declaration map.

* fix(release): keep node bundles readable for the worker image

The browser worker image lowers `yield*` only as a whole statement and
rewrites `for await` loops by their source layout, so compressed node-side
entries either fail that lowering or lower to unparseable code. Only the
browser bundles keep compression, which removes about a third of their text.

The bundle-purity assertion now matches the module-loader call regardless of
the quote style compression picks, and the release-pack fixture exercises
the installed package's new documentation requirement.

* feat(release): publish the whole documentation tree

`@deepseek-ai/dsh` copies `docs/` in full instead of the website's page
set: the maintainer documents, the generated migration records, the
translation guidelines, and the `*.i18n.yaml` consistency records ship
with the installed package, so the documentation's own links resolve
without a checkout.

* feat(release): publish the upgrade guides with the documentation

The staged documentation keeps the website's page set and adds every
`docs/upgrade-guide/` document, which is what an installed reader consults
when a released change breaks something. The rest of the maintainer tree
stays out.

* test(release): expect source and maps in master's new payload cases

* feat(release): keep installed docs and source readable in Desktop

Desktop keeps the published src/ and docs/ trees inside app.asar, and
every model shell call receives DSH_INSTALL_DIR and DSH_NODE_EXECUTABLE
so an agent can read them through Electron's Node mode. Staging also
generates docs/upgrade-guide/README.md, an index of the upgrade guides
grouped by the release they upgrade from.

Minified client bundles print string literals with backticks: the
worker image packer now recognizes those chunk requests, client license
banners are prepended after minification so their text survives, and
the affected artifact tests accept either quote.

* test(desktop): read archived docs through the Windows PowerShell form

Regenerate the config catalog for the shifted shell-env source line.

* test(desktop): wait for the GUI-subsystem Electron process from PowerShell

* build(release): publish source and maps from the message-quotes package

* build(release): publish source and maps from packages new on master

* build(release): publish source and maps from the tool-office package
2026-11-05 00:22:36 +08:00
..

description, kind
description kind
The web access service (ctx.web): how deployments and plugin authors search the web and fetch URLs through interchangeable providers, with one selection policy and error vocabulary. package-reference

@deepseek-ai/dsh-web

English | 中文

Summary

Use dsh-web to search the web or fetch a URL without tying callers to a specific vendor. It selects a usable backend for each operation and gives callers consistent cancellation, errors, and result limits. Choose it for plugins or tools that call ctx.web.search() or ctx.web.fetch(); the shipped dsh-tool-web tools load it for you. A search or fetch requires a configured, usable provider because this package does not make network requests on its own.

Table of Contents


Use this package

A composition that needs web access loads the dsh-web service and mounts at least one backend — a search provider and/or a fetch provider — and plugin or tool authors then call ctx.web.search() and ctx.web.fetch() directly. The service resolves the backend for each call, so callers never see provider ids unless they configured one.

When to choose it

Choose the service when a plugin or tool must search or fetch without hard-coding a vendor; a deployment that only uses the shipped web_search/web_fetch tools gets it for free through dsh-tool-web. You do not need it when the composition never reaches the web. The service adds no network access of its own: without at least one usable provider, every call fails with a structured WebError.

Minimal configuration

Load the service and let a single mounted backend auto-select, or pin a provider id with searchProvider/fetchProvider. The environment variables $DSH_WEB_SEARCH_PROVIDER and $DSH_WEB_FETCH_PROVIDER feed the same fields and are not a separate priority chain. Both backends below register a fetch provider, so the example pins fetchProvider: http.

- name: '@deepseek-ai/dsh-web'
  config:
    fetchProvider: http
- name: '@deepseek-ai/dsh-web-deepseek-api'
- name: '@deepseek-ai/dsh-web-fetch-http'
Field Default Meaning
searchProvider (unset) Pinned search provider id; unset auto-selects when exactly one is usable
fetchProvider (unset) Pinned fetch provider id; unset auto-selects when exactly one is usable

The generated configuration catalog is the exhaustive source for every accepted field and its JSDoc.

Searching and fetching

search() runs one query and returns an optional provider answer plus a list of citeable sources; the service enforces request.maxResults by truncating sources[] and setting truncated. fetch() retrieves one URL and returns its final URL, status code, decoded body, and a truncation flag; a non-2xx response is a result, not an error.

// Search the web; sources[] is capped to maxResults:
const result = await ctx.web.search({ query: 'deepseek harness', maxResults: 8 })

// Fetch one URL; a non-2xx response is a result, not an error:
const page = await ctx.web.fetch({ url: 'https://example.com' })

Both calls accept an optional AbortSignal that is forwarded to the provider for cancellation. The normalized request and result shapes are the contract callers build on; the vocabulary section of the web subsystem reference describes them exhaustively.

Provider selection

Each call resolves its provider at execution time, and registration or load order never matters. A configured provider id wins when it is registered and usable; without a configured id, the service runs the single usable provider or fails clearly:

Situation Outcome
configured id registered and usable runs that provider
configured id not registered WEB_PROVIDER_CONFIGURED_MISSING
configured id registered but unavailable WEB_PROVIDER_CONFIGURED_UNAVAILABLE
no id, exactly one registered usable provider runs it
no id, no usable provider WEB_PROVIDER_UNAVAILABLE
no id, multiple usable providers WEB_PROVIDER_AMBIGUOUS

The shipped deepseek-official and deepseek-search-api search ids select legacy Messages search and the direct Search API respectively.

A provider's availability is a cheap local check — for example whether its API key is present — and never makes network calls, so selection stays fast and deterministic.

Listing providers

listProviders() returns every registered search and fetch provider as { id, title?, description? } entries, each list in registration order; it lists unavailable providers too, because a picker shows what can be pinned. title is the provider's optional display name, and a picker shows id when it is absent. description is optional LocalizedText from dsh-package-manifest, a plain string or translations keyed by lowercase language id with a required en entry, stating what the provider suits and what it costs or cannot do; a picker shows it under the selected provider and shows nothing when it is absent. Each registration and each disposal emits the payload-free web/providers-changed event after the registry changes, and observer failures cannot veto the change. Clients call the same method as the web Remote namespace, ctx.remote.web.listProviders(), whose Remote result carries the lists in value only when ok is true, and refresh on the forwarded web/providers-changed event; the Web search and fetch settings page fills its Search and Fetch menus this way. A plugin (including one written in creator mode) that registers a provider with a title appears in the matching menu under that title, shows its description while selected, and choosing it pins searchProvider or fetchProvider to its id.

// Remote: web/listProviders; Host event: web/providers-changed
const response = await ctx.remote.web.listProviders()
if (response.ok) {
  const { search, fetch } = response.value
} // else: response.error is a structured RemoteError; keep any previous list and report it
ctx.remote.$on('web/providers-changed', () => { /* re-read the lists */ })

Add another provider

A search or fetch backend outside the shipped composition is a plugin package that calls ctx.web.registerSearchProvider() or ctx.web.registerFetchProvider() with a unique id, and an optional title. To enable one in a profile, install the package with dsh plugin --profile <name> add <package> (install rules), add its row to the profile's user patch layer $DSH_HOME/profiles/<name>/cordis.patch.yml, and restart DSH. That file is a top-level YAML array of patch entries; append the insert entry below, or replace a lone [] with it. The example mounts a hypothetical @example/dsh-web-search-acme; use the provider's real package name and add the config its README documents.

- insert:
    - id: web-search-acme
      name: '@example/dsh-web-search-acme'

dsh --profile <name> --dump-config prints the row under the patch file's layer. After the restart, the provider appears in the Web search provider or Web fetch provider menu of the settings page under its title, or its id when it has none; the page needs no change. Choosing it pins searchProvider or fetchProvider to its id. If the row is later removed while the provider is selected, the menu shows the stored id as (not loaded) and each call fails with WEB_PROVIDER_CONFIGURED_MISSING until another provider is chosen.

Search and fetch readiness

searchReady(llmProvider?) and fetchReady(llmProvider?) ask the provider that search() or fetch() would select whether an operation made for a model request on that chat route has a credential to send; dsh-tool-web uses the answers to decide whether each model step admits web_search and web_fetch. A provider answers through its optional ready(llmProvider), a configuration-only check that must not make network calls; a provider without it counts as ready. When selection fails, the readiness method answers true, because the operation reports that failure as a structured WebError. Readiness never changes which provider is selected.

// false only when the selected provider cannot authenticate this route's operations:
const searchOffered = await ctx.web.searchReady('deepseek-official')
const fetchOffered = await ctx.web.fetchReady('deepseek-official')

Failures and recovery

Failures throw WebError with a stable, machine-routable code; the message adds detail such as the missing provider id or the ambiguous candidate set. Callers route on the code and decide how to degrade. To change which backend a call uses, reconfigure the pinned id, mount or unmount providers, or fix the provider's configuration so its availability check passes.


Understand the implementation

Implementation internals — click to expand

This section explains the design decisions behind the service; the observable behavior is fully covered in Use this package.

Design philosophy

The package is built on one deliberate separation:

  • One seam, two independent operations. Search and fetch share no request schema and no business logic, but they share one service so provider selection, cancellation, errors, and product configuration have a single owner. The parallel Search/Fetch method pairs are intentional.
  • Selection is never order-dependent. A capability either pins a provider id or auto-selects when exactly one usable provider is registered; search()/fetch() resolve the provider at execution time.
  • The service owns the result bound. maxResults is enforced by the seam after the provider returns, so an over-returning provider can never leak more sources than the caller asked for.

Source map

File Role
src/index.ts Plugin entry: the WebRuntime service, both provider registries, the web/listProviders Remote method and web/providers-changed event, execution-time selection, and per-route readiness
src/types.ts Vocabulary: request/result types, provider list entries, the web/providers-changed event declaration, the closed WebFetchBody union, and the WebError taxonomy

Data model

The request and result types define the normalized vocabulary callers build on — one Search pair and one Fetch pair — and the exhaustive fields and JSDoc live in src/types.ts and the web subsystem reference. Two deliberate choices shape them: WebFetchBody is a closed union (html | text) owned here, so adding a kind breaks compilation until every consumer handles it; WebError extends HarnessError with an open-string code, so consumers must tolerate provider-specific values. Source fields stay optional because not every provider returns all of them.

Selection flow

At call time the service resolves the provider — configured id first, then the unique usable provider — and throws the matching WebError when no clear winner exists. A search result then passes through capSources, which truncates sources[] to maxResults and flags truncated. searchReady() and fetchReady() resolve the same provider as the matching operation, answer true when resolution throws, and otherwise return the provider's ready() answer, or true when it has none. Registration is effect-based: providers register with the calling fiber and unregister when it disposes, and a duplicate id within a capability kind is rejected at registration.


Further Exploration

Read these pages when the package-level contract is not enough. They move from the shared vocabulary to the shipped backends, the model-facing tools, and the design rationale.


Model Experience

Indirectly, through dsh-tool-web, which renders the seam's normalized search results and fetch bodies to the model and does not admit web_search or web_fetch to model steps for which searchReady() or fetchReady() answers false, while this service contributes no prompt or schema.

KV Cache effect

No direct invalidation; the named consumer owns any request-prefix changes.

Known Limitations and Deferred Work

These limits define when the service is incomplete on its own. They are current package constraints.

  • No availability observation — listProviders() and web/providers-changed report which providers are registered, not whether they are usable; there is no capability-status query, and searchReady() and fetchReady() report only the selected provider's credential readiness and answer true when selection fails. Availability is observable only by running a search or fetch and routing the thrown code, and the no-provider failure is the generic WEB_PROVIDER_UNAVAILABLE with no per-provider reason enumeration (Agent Note).
  • Search requests carry only query and maxResults — provider-neutral controls (recency, domain filters, regional hints, search depth) are deferred until the backends can honor them (seam Agent Note).
  • WebFetchBody has no pdf arm — text-extractable PDF support is named deferred work; the closed union makes adding it a compile-enforced change across the web packages.
  • Provider-backed page extraction is out of scope of fetch() — a Firecrawl/Tavily-style web_extract capability is deferred rather than widening the fetch operation.

Dev Note

Working context for maintainers — click to expand

This Dev Note is working context for maintainers: open questions and undecided directions. It is explicitly non-authoritative — shipped behavior, limits, and rationale live in the sections above and the linked Agent Notes.

Future: observing provider state

Registration is observable through listProviders() and web/providers-changed, but no capability-status query exists; consumers observe availability only by executing a call and routing the thrown code. Restoring a small observation surface is possible if a consumer needs per-provider reasons, but the archived simplification note records why the earlier one was dropped.