# Provider-aware Web search

Target: official DSH / Windows Desktop `0.2.0-rc.2`. The routed facade and
configuration page are shipped plugin functionality, not a pending proposal.

## Choose who searches

Open **Plugins → dsh-github-copilot → Details → Web search**.

| Control | Behavior |
|---|---|
| Search provider: Auto | Follow the initiating Chat provider when its exact supported search registration exists |
| Search provider: an ID | Always use that registered provider, independent of later Chat model changes |
| Fallback provider | At most one different final provider after an eligible failure; None disables it |

Saving both routing leaves uses one native settings CAS, with no Copilot
credential, model discovery or capability-probe prerequisite. Saving does not
prove a provider is available. The directory lists fresh public registrations,
not static examples; unavailable stored IDs stay visible and fail explicitly.
Reload sees newly registered IDs.

![Actual parent-following and search controls with synthetic settings](images/copilot-search-routing.png)

The picture uses current built Client components in an isolated synthetic browser fixture.
It does not demonstrate a live search or loaded Desktop runtime.

## Routing and fallback

Auto uses the captured initiating Session/request route, never the global Chat
default or another Session. Copilot aliases keep their account/model checks;
other IDs match an observed search registration exactly. Model names/families
do not become search providers. This provider-level convention does not promise
the same account or execution model across independent registries.

A successful primary result, including zero sources, ends routing. Failure may
try one distinct final fallback, never the same backend twice, a hidden third
tier or recursive routing. Cancellation, disposal, registration replacement
and account-proof invalidation terminate the operation. Captured implementations,
not only replaceable IDs, remain bound to the request.

Fallback notices survive native result projection in `WebSearchResult.content`,
identify the backend and possible charges, and expose no raw exception.
Generic providers own their authentication and cancellation; their endpoint/model
provenance is not guessed from Chat. The stronger Copilot/legacy pre-dispatch
proof guard is not claimed for every third-party implementation.

## Copilot model selection and proof

Auto-following Copilot search uses the initiating real model. Explicit/fallback
Copilot search instead preserves a nonempty `github-copilot.searchModel`
override; otherwise it non-forcing ensures current account metadata and
considers at most three eligible Responses models in deterministic ID order.
No model-name table, Session borrowing or global default supplies candidates.

Metadata establishes eligibility, not search capability. Default `probe: true`
requires bounded successful proof before **one final user-query request**.
Candidate proof is not query replay; a final failure never tries the query on
another model. `probe: false` is an explicit trust override, not a recommended
repair. Cancellation or captured credential/account-proof changes fail closed.
Successful reusable proofs retain account/owner lifetime, not the creating
request's signal/deadline. Concurrent callers own their unfinished probes.

## Settings and legacy compatibility

Routing lives in `github-copilot.searchRouting`. One CAS writes nested paths
`['searchRouting', 'searchProvider']` and
`['searchRouting', 'defaultSearchProvider']`. Legacy separate namespaces are
read-only fallback sources. Old fixed/none spending restrictions are preserved
until an explicit save adopts new policy; unknown IDs never silently normalize
to an available provider.

A nonempty legacy `searchModel` is displayed read-only and remains authoritative.
Only its separate reset writes `searchModel: ''`. Both actions coordinate the
shared namespace revision without discarding unsaved drafts. A failed reset
does not block an unrelated routing save.

Official SettingsForms projects volatile Config leaves; the plugin uses native
live fields and narrow writes, not a manually invented namespace registration.
Traced Settings/routing Remote faces are captured once at UI registration so
parent renders retain drafts and pending saves. `routeWebSearch`, enable,
allowlists and capability proof keep independent responsibilities. Safety and
transport settings are not routine fields on this card.

## Composition and installation safety

The plugin moves the original enabled top-level official Web service into
`github-copilot-original-web`, exposes it through a public Fiber-owned delegate,
and mounts its routed facade in ordinary Host Web scope. Original configuration,
fetch and native tool consumers remain under their owners. A separate private
official WebRuntime retains request validation/source capping for routed search.
No preset edits, private registry access or Core prototype replacement occurs.

Before install/update, run the verified package's read-only
`scripts/check-search-composition.mjs` with profile, home, install anchor and
all launcher patches. Require `supported: true`. It parses public composition
without booting plugins, credentials, network or settings writes and compares
the baseline with the actual candidate bundle position.

Custom/dynamic/nested/disabled/already-isolated layouts, collisions and a nonempty
disposable `cordis.yml` root are refused. Normal `composeProfile()` rewrites that
root to `[]`, so preserve it and review complete reconstructibility before any
separately approved normalization. `dsh plugin add` does not enforce preflight.
Do not describe unsupported composition as a safe no-op.

Desktop supports its qualified bundled CLI, not any generic shim.
[Installation guidance](./npm-distribution.md#desktop-bundled-cli-on-official-rc2).
Web service disable/remove/upgrade may require a cold restart; do not use
remove/re-add as a hot-upgrade workaround.
[Lifecycle characterization](./web-lifecycle-rc2.md).

## Verification and limits

Real Loader/ConfigEditor/SettingsForms fixtures cover volatile discovery,
two-leaf CAS, same Fiber, stale conflicts, hidden legacy reset and restart
persistence. Client gateway/actual registration tests cover traced identities,
draft continuity and catalog reads; a settings-map mock alone cannot establish
native reachability. Native WebRuntime/Agent/tool fixtures cover validation,
registration disposal, result notices and original fetch ownership.

Policy and transport tests cover bounded candidates, lazy account discovery,
proof expiry, fallback auth, credential discontinuity, cancellation and
independent requests. Published Client desktop/narrow captures use synthetic
services. Required exact rc.2 Windows/Linux CI remains the release gate; counts
and old local registry failures belong in the relevant PR evidence, not permanent
setup instructions.

Live hosted-search capability, paid fallback behavior and Desktop activation
require separate authorized acceptance. Ordinary Chat success, saved routing,
synthetic tests or a source marker prove none of those layers.
Design history: [#135](https://github.com/cloga/dsh-github-copilot/issues/135),
[#148](https://github.com/cloga/dsh-github-copilot/issues/148),
[#195](https://github.com/cloga/dsh-github-copilot/issues/195) and
[#216](https://github.com/cloga/dsh-github-copilot/issues/216).
