diff --git a/.agents/notes/implemented/architecture/2026-09-27-web-search-offered-by-provider-readiness.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-27-web-search-offered-by-provider-readiness.i18n.yaml index d1c61d03ae..f56546acb0 100644 --- a/.agents/notes/implemented/architecture/2026-09-27-web-search-offered-by-provider-readiness.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-27-web-search-offered-by-provider-readiness.i18n.yaml @@ -15,5 +15,5 @@ en: 72f3927c7d858be3 zh: 0ed84e51d2e6db9f /agent-note-offer-web-search-only-while-the-selected-search-provider-is-ready/consequences: - en: 328498359d32cadf - zh: aac5e93f75e05ef5 + en: d857dbff3551a94a + zh: 3d554d74388af801 diff --git a/.agents/notes/implemented/architecture/2026-09-27-web-search-offered-by-provider-readiness.md b/.agents/notes/implemented/architecture/2026-09-27-web-search-offered-by-provider-readiness.md index aa05f62155..b277d1b18f 100644 --- a/.agents/notes/implemented/architecture/2026-09-27-web-search-offered-by-provider-readiness.md +++ b/.agents/notes/implemented/architecture/2026-09-27-web-search-offered-by-provider-readiness.md @@ -51,6 +51,6 @@ The request header records the assembled tools, so the agent loop announces each ## Consequences -`dsh-web` gains two public members, the optional `WebSearchProvider.ready()` and `WebRuntime.searchReady()`. `searchReady()` is the smallest query its one consumer needs, which is how the [observation-surface decision](../../archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md) lets a status query return; it reports neither availability nor selection failures. The Exa and Perplexity providers implement no `ready()`, so their behavior is unchanged. PTC presentations keep offering `web_search` without credentials. An endpoint that cannot serve the native server tool keeps the tool offered and fails each call. Removing the tool on an unchanged route changes the declared tools and misses the prompt cache once; a model switch already invalidates it. Searching from another provider's conversation requires search's own key or endpoint, and writing the shipped default values counts as no configuration. Reading the official chat endpoint makes this provider depend on `dsh-llm-deepseek-api-key` and on the Loader's rows; replay compositions disable that row and so skip the endpoint check. +`dsh-web` gains two public members, the optional `WebSearchProvider.ready()` and `WebRuntime.searchReady()`. `searchReady()` is the smallest query its one consumer needs, which is how the [observation-surface decision](../../archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md) lets a status query return; it reports neither availability nor selection failures. The Exa and Perplexity providers implement no `ready()`, so their behavior is unchanged. PTC presentations keep offering `web_search` without credentials. An endpoint that cannot serve the native server tool keeps the tool offered and fails each call. Removing the tool on an unchanged route changes the declared tools and misses the prompt cache once; a model switch already invalidates it. Searching from another provider's conversation requires search's own key or endpoint, and writing out the default key reference `DEEPSEEK_API_KEY` counts as no configuration, while a written `baseURL` counts even when it names the default endpoint. Reading the official chat endpoint makes this provider depend on `dsh-llm-deepseek-api-key` and on the Loader's rows; replay compositions disable that row and so skip the endpoint check. Unit tests pin each role: `packages/web/web/tests/web.spec.ts` covers which provider `searchReady()` asks and its answer on selection failure, `packages/web/web-search-deepseek/tests/deepseek.spec.ts` covers each credential case of `ready()`, and `packages/web/tool-web/tests/tool-web.spec.ts` assembles through the real prompt, tool, and model-selection services. The shipped headless keyless smoke test and the `web-search-unconfigured` recorded-session snapshot pin the withheld tool in real compositions. Snapshot replays supply a placeholder DeepSeek key, so their tool lists do not depend on the runner's environment. `packages/web/web-search-deepseek/tests/chat-endpoint.spec.ts` boots a real Loader composition with the official chat route to pin the endpoint check, and the `pi-ai-conversation-updates` snapshot pins that a conversation on a pi-ai route is not offered `web_search`. diff --git a/.agents/notes/implemented/architecture/2026-09-27-web-search-offered-by-provider-readiness.zh.md b/.agents/notes/implemented/architecture/2026-09-27-web-search-offered-by-provider-readiness.zh.md index 99a32304b5..aa9e3c1253 100644 --- a/.agents/notes/implemented/architecture/2026-09-27-web-search-offered-by-provider-readiness.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-27-web-search-offered-by-provider-readiness.zh.md @@ -51,6 +51,6 @@ API 密钥能否计入,取决于是否为搜索单独做了配置:字面 `ap ## Consequences -`dsh-web` 增加两个公共成员:可选的 `WebSearchProvider.ready()` 与 `WebRuntime.searchReady()`。`searchReady()` 是其唯一消费方所需的最小查询,这正是[观测接口决策](../../archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md)允许状态查询回归的方式;它既不报告可用性,也不报告选择失败。Exa 与 Perplexity 提供方没有实现 `ready()`,其行为不变。PTC 呈现在没有凭据时仍然提供 `web_search`。无法提供原生服务器工具的端点仍会让工具保持提供,并使每次调用失败。在路由不变时移除工具会改变声明的工具,并使提示词缓存失效一次;模型切换本身已经使缓存失效。在其他提供方的对话中搜索,需要为搜索单独配置密钥或端点;写出发行配置的默认值不算单独配置。读取官方聊天地址让本提供方依赖 `dsh-llm-deepseek-api-key` 与 Loader 的行;回放组合禁用了该行,因此跳过端点检查。 +`dsh-web` 增加两个公共成员:可选的 `WebSearchProvider.ready()` 与 `WebRuntime.searchReady()`。`searchReady()` 是其唯一消费方所需的最小查询,这正是[观测接口决策](../../archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md)允许状态查询回归的方式;它既不报告可用性,也不报告选择失败。Exa 与 Perplexity 提供方没有实现 `ready()`,其行为不变。PTC 呈现在没有凭据时仍然提供 `web_search`。无法提供原生服务器工具的端点仍会让工具保持提供,并使每次调用失败。在路由不变时移除工具会改变声明的工具,并使提示词缓存失效一次;模型切换本身已经使缓存失效。在其他提供方的对话中搜索,需要为搜索单独配置密钥或端点;写出默认的密钥引用 `DEEPSEEK_API_KEY` 不算单独配置,而写出的 `baseURL` 即使就是默认端点也算。读取官方聊天地址让本提供方依赖 `dsh-llm-deepseek-api-key` 与 Loader 的行;回放组合禁用了该行,因此跳过端点检查。 单元测试分别固定每个角色:`packages/web/web/tests/web.spec.ts` 覆盖 `searchReady()` 询问哪个提供方以及选择失败时的回答,`packages/web/web-search-deepseek/tests/deepseek.spec.ts` 覆盖 `ready()` 的每种凭据情况,`packages/web/tool-web/tests/tool-web.spec.ts` 通过真实的提示词、工具与模型选择服务进行组装。发行版 headless 无密钥冒烟测试与 `web-search-unconfigured` 录制会话快照在真实组合中固定了撤下行为。快照回放提供占位 DeepSeek 密钥,因此其工具列表不依赖运行环境。`packages/web/web-search-deepseek/tests/chat-endpoint.spec.ts` 用真实 Loader 组合挂载官方聊天路由,固定端点检查;`pi-ai-conversation-updates` 快照固定了 pi-ai 路由上的对话不会获得 `web_search`。 diff --git a/packages/web/web-search-deepseek/README.i18n.yaml b/packages/web/web-search-deepseek/README.i18n.yaml index cdb53838f2..9646594e7f 100644 --- a/packages/web/web-search-deepseek/README.i18n.yaml +++ b/packages/web/web-search-deepseek/README.i18n.yaml @@ -90,8 +90,8 @@ en: 1d836a33dfa97fcf zh: 2593ccf2a5716b02 /deepseek-ai-dsh-web-search-deepseek/known-limitations-and-deferred-work: - en: 779c0cc22ad34cdb - zh: e0cb611e90d0530f + en: 3fa3dcad9dc45edd + zh: 8796dbf65ec807eb /deepseek-ai-dsh-web-search-deepseek/known-limitations-and-deferred-work/dev-note: en: 7f775cc79dbce94b zh: 12b8f68f9e7c48ca diff --git a/packages/web/web-search-deepseek/README.md b/packages/web/web-search-deepseek/README.md index 4f692c7992..2ba276b840 100644 --- a/packages/web/web-search-deepseek/README.md +++ b/packages/web/web-search-deepseek/README.md @@ -182,7 +182,7 @@ These limits define when the provider is expensive or incomplete. They are curre - **Provider selection cannot see a missing key** — `available()` is synchronous and cannot query the asynchronous credential store, so `ctx.web` still selects a keyless provider and a direct search fails with `WEB_PROVIDER_CREDENTIAL_MISSING`; the asynchronous `ready()` affects only which model requests are offered `web_search`. - **Readiness checks presence, not validity** — a wrong key, or an endpoint without the native `web_search_20250305` server tool, counts as ready, so `web_search` stays offered and each call fails with a structured error. - **The endpoint check compares origins only** — a gateway on another origin that forwards an official DeepSeek key is still refused until search has its own key or endpoint. -- **Configuring the default values counts as no configuration** — `apiKeyEnv: DEEPSEEK_API_KEY` written out matches the shipped row, so it keeps the DeepSeek-route and endpoint checks. +- **Only the key reference is compared with its default** — `apiKeyEnv: DEEPSEEK_API_KEY` written out matches the shipped row, so it keeps the DeepSeek-route and endpoint checks; a written `baseURL` counts as search configuration even when it names the default endpoint. - **Over-returned sources still cost tokens** — with no result-count knob on the wire, `maxResults` is enforced only post-hoc by service truncation. - **Uncited results carry no `snippet`** — a source gains one only when a text-block citation (`cited_text`) matches its URL. diff --git a/packages/web/web-search-deepseek/README.zh.md b/packages/web/web-search-deepseek/README.zh.md index 08b9ddfd23..d90bfe843b 100644 --- a/packages/web/web-search-deepseek/README.zh.md +++ b/packages/web/web-search-deepseek/README.zh.md @@ -182,7 +182,7 @@ kind: "package-reference" - **提供方选择看不到缺失的密钥**——`available()` 是同步的,无法查询异步凭据存储,因此 `ctx.web` 仍会选中没有密钥的提供方,直接搜索会以 `WEB_PROVIDER_CREDENTIAL_MISSING` 失败;异步的 `ready()` 只影响哪些模型请求会获得 `web_search`。 - **就绪判断只检查凭据是否存在,不检查是否有效**——错误的密钥,或不提供原生 `web_search_20250305` 服务器工具的端点,都视为就绪,因此 `web_search` 继续提供,每次调用都以结构化错误失败。 - **端点检查只比较源**——位于其他源、转发官方 DeepSeek 密钥的网关仍会被拒绝,直到为搜索单独配置密钥或端点。 -- **写出默认值不算单独配置**——显式写出的 `apiKeyEnv: DEEPSEEK_API_KEY` 与发行配置行相同,因此仍受 DeepSeek 路由与端点检查约束。 +- **只有密钥引用会与默认值比较**——显式写出的 `apiKeyEnv: DEEPSEEK_API_KEY` 与发行配置行相同,因此仍受 DeepSeek 路由与端点检查约束;写出的 `baseURL` 即使就是默认端点,也算为搜索单独配置。 - **超量返回的来源仍消耗 token**——协议没有结果数量旋钮,`maxResults` 只能由服务在事后截断。 - **未引用的结果没有 `snippet`**——只有当文本块引用(`cited_text`)匹配其 URL 时,来源才会获得 snippet。