DSH / Atlas
2026-07-31implementedfeaturearchived 2026-09-30

Default Web search in shipped compositions

已交付组合中的默认 Web 搜索

The harness had a complete Web capability family—provider registry, DeepSeek/Exa/Perplexity search providers, local fetch, stable model tools, and structured result presentation—but the shipped `dsh web` composition mounted none of it. The model could not discover current information unless a deployment supplied a custom overlay. Merely mounting the existing DeepSeek provider would not complete the WebUI path: the Mo

English

Problem

The harness had a complete Web capability family—provider registry, DeepSeek/Exa/Perplexity search providers, local fetch, stable model tools, and structured result presentation—but the shipped dsh web composition mounted none of it. The model could not discover current information unless a deployment supplied a custom overlay. Merely mounting the existing DeepSeek provider would not complete the WebUI path: the Models page stores DEEPSEEK_API_KEY through ctx.credentials, while the search provider froze only the process environment at plugin load, so a key entered or rotated in the running UI would not reach search.

Decision

packages/bundle/base/cordis.patch.yml explicitly mounts dsh-web with searchProvider: deepseek-official and fetchProvider: http, dsh-web-search-deepseek, dsh-web-fetch-http, and dsh-tool-web with searchTimeoutMs: 60000. The shared-base Web fetch default owns the current fetch: true; this record continues to own provider selection, search credentials, and timeout. Explicit provider ids keep selection independent of registration order and leave personal or --patch overlays able to replace or disable the rows. The one-minute shipped budget covers an auxiliary DeepSeek Messages request plus server-side retrieval while leaving dsh-tool-web's provider-neutral 30-second default unchanged for custom compositions. The Web capability seam decision owns the public-fetch security policy.

DeepSeek search uses the same DEEPSEEK_API_KEY credential reference as the official conversation adapter. The provider resolves that reference inside every search through the optional ctx.credentials service; only a composition without the seam falls back to the launching process environment, and a non-empty literal apiKey remains the programmatic last resort. A stored or rotated Web Models key therefore reaches the next search without restarting or retaining the value on the provider. Because WebSearchProvider.available() is synchronous, it treats an installed resolver as locally usable and missing dynamic credentials fail the operation with the provider-specific WEB_PROVIDER_CREDENTIAL_MISSING code while the stable tool schema stays registered.

Search keeps its endpoint distinct from chat completions: DEEPSEEK_SEARCH_BASE_URL overrides the Anthropic-compatible base, while DEEPSEEK_BASE_URL continues to configure conversation requests. Each web_search performs an auxiliary DeepSeek Messages call with the native search server tool. Immediately before dispatch, the provider appends a log-only web/deepseek-search-llm-request event to the initiating Agent session with the resolved endpoint, API version, and exact secret-free JSON body. A failure after dispatch names that endpoint and tells the conversation model to guide the user to the Web search Endpoint field in Settings when the endpoint is unintended. The message names DEEPSEEK_SEARCH_BASE_URL and web-search-deepseek.baseURL when that settings page is unavailable; the model does not select or change the credential destination. Credential preflight remains provider-local and races caller cancellation; neither concern expands the generic Web or credentials seams.

The default mount does not create a Web-specific permission policy. web_search and enabled web_fetch calls execute outside the shell/filesystem sandbox and approval presets, following dsh-tool-web's existing contract. The HTTP provider restricts fetches to validated public destinations, but it does not constrain public data egress. The shipped workspace-write default governs file mutations only; a restricted-network product stance requires a tools/pre-execute policy or capability-specific network confinement rather than implying that filesystem access mode governs Web calls.

Alternatives considered

Mount only dsh-tool-web. Rejected because stable schemas without registered providers would make every default call fail; enablement and backend availability are deliberately separate, but a shipped default must supply its intended implementations.

Read $DSH_HOME/.env from cordis.yml or hoist it into process.env. Rejected because the credential provider owns that document, environment values are read-only overrides, and hoisting would make stored keys unrotatable while bypassing the audited secret boundary.

Freeze process.env.DEEPSEEK_API_KEY at provider load. Rejected because the Web Models page writes through ctx.credentials; the product's documented first-run path must make the next operation work without a restart.

Keep Web tools in web.cordis.yml. Rejected because it preserves an unexplained tool-roster difference between TUI and Web/headless. The rows are not surface-specific, so base.cordis.yml is their one home; the tool-roster decision records the shared composition.

Raise dsh-tool-web's provider-neutral timeout. Rejected because custom providers and deployments own different latency expectations; the shipped DeepSeek composition owns this deployment budget.

Enable fetch on every shared-base surface. This record rejected the alternative because shared-base products could require different network policies. The shared-base Web fetch default supersedes that rejection after the shipped products converged on one full tool roster; its public-destination and no-approval constraints remain current.

Consequences

Native model requests on headless, full SDK, ACP, and custom base-only profiles carry the web_search and web_fetch schemas and guidance; Web presets expose the same pair, including beneath run_code in PTC mode. Search adds a complete auxiliary model call and may use the native server tool multiple times; its exact secret-free request remains reconstructable from the initiating session log. Fetch enforces public addresses and requires no per-call approval. The Web snapshot lane boots the shipped tree, drives a replayed web_search call through the real DeepSeek provider against a local Messages fixture, asserts the durable auxiliary request and structured result, and pins the settled browser presentation. Shared snapshot headers pin the common fetch schema and prompt guidance. Composition smokes pin the tool roster; the built composition dump pins the one-minute shipped search budget; provider tests pin missing, stored, and rotated credential behavior plus literal and ambient compatibility.

中文

问题

该 harness 已具备完整的 Web 能力体系:提供方注册表、DeepSeek、Exa 和 Perplexity 搜索提供方、本地抓取、稳定的面向模型工具,以及结构化结果呈现,但已交付的 dsh web 组合没有挂载其中任何一项。除非部署提供自定义覆盖层,否则模型无法发现最新信息。仅挂载现有 DeepSeek 提供方仍无法打通 WebUI 链路:Models 页面通过 ctx.credentials 存储 DEEPSEEK_API_KEY,而搜索提供方只会在插件加载时固定读取进程环境,因此在运行中的 UI 输入或轮换的密钥无法用于搜索。

决策

packages/bundle/base/cordis.patch.yml 明确挂载 dsh-web,配置 searchProvider: deepseek-official 与 fetchProvider: http,同时挂载 dsh-web-search-deepseek、dsh-web-fetch-http,并以 searchTimeoutMs: 60000 挂载 dsh-tool-web。共享 base 的 Web 抓取默认值负责当前的 fetch: true;本文继续负责提供方选择、搜索凭据与超时。显式提供方 id 使选择不受注册顺序影响,同时个人覆盖层或 --patch 覆盖层仍可替换或禁用这些配置项。已交付的一分钟预算用于覆盖一次辅助 DeepSeek Messages 请求及服务端检索,同时保持 dsh-tool-web 提供方无关的 30 秒默认值不变,以供自定义组合使用。Web 能力 seam 决策负责公开抓取安全策略。

DeepSeek 搜索使用与官方会话适配器相同的 DEEPSEEK_API_KEY 凭据引用。提供方在每次搜索内部通过可选的 ctx.credentials 服务解析该引用;只有未挂载该 seam 的组合才会回退到启动进程的环境变量,非空的 apiKey 字面值仍作为程序化配置的最后兜底。因此,由 Web 的 Models 页存储或轮换的密钥无需重启即可用于下一次搜索,提供方也无需保留该值。由于 WebSearchProvider.available() 是同步方法,它会将已安装解析器视为本地可用;若动态凭据缺失,操作会以提供方专属错误码 WEB_PROVIDER_CREDENTIAL_MISSING 失败,而稳定的工具 schema 仍保持注册。

搜索端点与 chat completions 保持独立:DEEPSEEK_SEARCH_BASE_URL 覆盖 Anthropic 兼容基址,DEEPSEEK_BASE_URL 则继续配置会话请求。每次 web_search 都会发起一次辅助 DeepSeek Messages 调用,并携带原生搜索服务器工具。发出请求前一刻,提供方会向发起请求的 agent(智能体)会话追加仅用于日志的 LLM(大语言模型)请求事件 web/deepseek-search-llm-request,其中包含已解析端点、API 版本,以及不含密钥的精确 JSON 请求体。请求发出后的失败会指出该端点;当端点不符合用户预期时,错误消息会要求会话模型指导用户在 Settings 中修改网页搜索的 Endpoint 字段。该设置页面不可用时,消息会说明 DEEPSEEK_SEARCH_BASE_URL 和 web-search-deepseek.baseURL;模型不得替用户选择或修改凭据发送目的地。凭据预检仍留在提供方内部,并与调用方取消存在竞态;这两项关注点都不会扩展通用 Web seam 或凭据 seam。

默认挂载不会创建 Web 专用权限策略。web_search 与已启用的 web_fetch 调用会在 bash/文件系统沙箱及审批 preset 之外执行,并遵循 dsh-tool-web 的现有约定。HTTP 提供方把抓取限制到已验证的公开目的地址,但不限制公开数据出站。已交付的 workspace-write 默认值只管辖文件修改;若产品采取受限网络策略,就需要添加 tools/pre-execute 策略或按能力限制网络访问,而不能暗示文件系统访问模式会管辖 Web 调用。

考虑过的替代方案

仅挂载 dsh-tool-web。 不予采纳:稳定的 schema 如果没有已注册提供方,每次默认调用都会失败。启用状态与后端可用性刻意分离,但已交付的默认配置必须提供其预期实现。

从 cordis.yml 读取 $DSH_HOME/.env,或将其提升到 process.env。 不予采纳:凭据提供方拥有该文件,环境变量值是只读覆盖;提升后存储的密钥将无法轮换,还会绕过经审计的密钥边界。

在提供方加载时固定读取 process.env.DEEPSEEK_API_KEY。 不予采纳:Web Models 页面通过 ctx.credentials 写入密钥;产品文档规定的首次运行路径必须保证下一次操作无需重启即可生效。

将 Web 工具保留在 web.cordis.yml 中。 不予采纳:这会保留 TUI 与 Web/无头界面之间无法解释的工具清单差异。这些配置行并非界面特有,因此其唯一归属是 base.cordis.yml;工具清单决策记录了这一共享组合。

提高 dsh-tool-web 的提供方无关超时。 不予采纳:自定义提供方和部署有各自不同的延迟预期;这一部署预算应归已交付的 DeepSeek 组合所有。

在每个共享 base surface 上启用抓取。 本文曾因各产品可能需要不同网络策略而否决该方案。已交付产品采用同一个完整工具集合后,共享 base 的 Web 抓取默认值取代了该否决;仅限公开目的地址与无需逐次审批的约束仍然有效。

后果

headless、完整 SDK、ACP 与仅使用 base 的自定义 profile 的原生模型请求都会携带 web_search 和 web_fetch schema 与指引;Web preset 会暴露同一对工具,PTC mode 还会通过 run_code 暴露它们。搜索会增加一次完整的辅助模型调用,并可能多次使用原生服务器工具;发起会话的日志仍可精确重建其不含密钥的请求。抓取会强制使用公开地址,并且无需逐次审批。Web 快照通道会启动已交付配置树,使用本地 Messages fixture(测试前置数据),经由真实 DeepSeek 提供方驱动一次回放的 web_search 调用,断言持久化的辅助请求与结构化结果,并固定最终浏览器呈现。共享 snapshot header 会固定通用的抓取 schema 与提示指引。组合冒烟测试会固定工具集合;构建后组合配置的转储固定已交付的一分钟搜索预算;提供方测试固定缺失、已存储及已轮换凭据的行为,以及字面值与环境变量的兼容性。