Merge branch 'master' into codex/project-instruction-files

This commit is contained in:
Yichen Jiang
2026-07-15 13:36:53 +08:00
committed by GitHub
35 changed files with 250 additions and 354 deletions

View File

@@ -1041,7 +1041,7 @@ export interface WebServiceConfig {
}
```
Source: [`packages/web/web/src/index.ts:59`](../packages/web/web/src/index.ts)
Source: [`packages/web/web/src/index.ts:55`](../packages/web/web/src/index.ts)
## `@deepseek-ai/dsh-web-fetch-local`
@@ -1056,10 +1056,8 @@ export interface Config {
maxResponseBytes?: number
/** Maximum decoded body length in characters. */
maxBodyChars?: number
/** Default fetch timeout in milliseconds. */
/** Default fetch timeout in milliseconds, within Node's timer range. */
timeoutMs?: number
/** Upper bound for a per-request timeout override. */
maxTimeoutMs?: number
/** Maximum number of same-origin redirect hops to follow. */
maxRedirects?: number
/** `User-Agent` header sent on every request. */
@@ -1067,7 +1065,7 @@ export interface Config {
}
```
Source: [`packages/web/web-fetch-local/src/index.ts:34`](../packages/web/web-fetch-local/src/index.ts)
Source: [`packages/web/web-fetch-local/src/index.ts:36`](../packages/web/web-fetch-local/src/index.ts)
## `@deepseek-ai/dsh-web-search-deepseek`

View File

@@ -276,7 +276,7 @@ The web access service. Registered as `ctx.web` (one instance per context).
Selection semantics (resolved at execution time, never order-dependent):
- A configured id that is registered and `status().available` → that provider.
- A configured id that is registered and `available()` → that provider.
- A configured id not registered → `WEB_PROVIDER_CONFIGURED_MISSING`.
- A configured id registered but unavailable → `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`.
- No id configured, exactly one registered usable provider → that provider.
@@ -286,11 +286,11 @@ Selection semantics (resolved at execution time, never order-dependent):
```ts cordis-catalog
registerSearchProvider(provider: WebSearchProvider): () => void
registerFetchProvider(provider: WebFetchProvider): () => void
async search(request: WebSearchRequest, exec?: WebExecContext): Promise<WebSearchResult>
async fetch(request: WebFetchRequest, exec?: WebExecContext): Promise<WebFetchResult>
async search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>
async fetch(request: WebFetchRequest, signal?: AbortSignal): Promise<WebFetchResult>
```
Source: [`packages/web/web/src/index.ts:78`](../../packages/web/web/src/index.ts)
Source: [`packages/web/web/src/index.ts:74`](../../packages/web/web/src/index.ts)
## `ctx.workflows` — `WorkflowService` (abstract seam)

View File

@@ -31,7 +31,7 @@ Everything else is documented on a **sub-page**, not here. The rule that draws t
| [skills.md](skills.md) | the skill service: discovery priority, `SkillSummary`/`SkillDefinition`, session-prefix catalog, model-facing `skill` loading |
| [compaction.md](compaction.md) | the compaction seam: the `compact/*` session events, `CompactionResult`, the `CompactService` interface |
| [subagent.md](subagent.md) | the subagent seam: the named-provider registry, `SubagentStartRequest`/`Result`/`Run`, the start-time-vs-runtime capability split |
| [web.md](web.md) | the web access seam: `WebSearchRequest`/`Result`, `WebFetchRequest`/`Result`, `WebFetchBody`, provider/capability status, `WebError` |
| [web.md](web.md) | the web access seam: `WebSearchRequest`/`Result`, `WebFetchRequest`/`Result`, `WebFetchBody`, provider availability, `WebError` |
| [workflow.md](workflow.md) | the workflow seam: `WorkflowStartRequest`, `WorkflowMeta`, `WorkflowRun`/`Result`, the `workflow/*` event payloads, `WorkflowError` fatality |
> Type definitions on this page are pasted **verbatim** from source and drift-checked by `pnpm run verify-type-equiv` (see [development.md](../development.md#documenting-types-verbatim-ts-type-equiv)). Inline JSDoc is omitted for readability; follow the source link for the full contracts.

View File

@@ -25,8 +25,6 @@ interface WebSearchRequest {
```ts type-equiv
interface WebSearchResult {
readonly providerId: string
readonly query: string
readonly content?: string
readonly sources: readonly WebSearchSource[]
readonly truncated: boolean
@@ -49,7 +47,6 @@ interface WebSearchSource {
```ts type-equiv
interface WebFetchRequest {
readonly url: string
readonly timeoutMs?: number
}
```
@@ -57,7 +54,6 @@ HTTP status is part of the fetched resource state, not automatically a failure:
```ts type-equiv
interface WebFetchResult {
readonly providerId: string
readonly url: string
readonly statusCode: number
readonly body: WebFetchBody
@@ -73,15 +69,9 @@ type WebFetchBody =
| { readonly kind: 'text'; readonly content: string }
```
## Provider status
## Provider availability
A provider's `status()` is a cheap LOCAL check (credential presence, parseable config) and **must not make network calls**. It is an input to execution-time selection, not a health system: `search()`/`fetch()` read it to pick a usable provider, and a selection failure surfaces as the structured `WebError` the caller routes on — which carries the branchable detail (the missing id, the ambiguous candidate set) in its code and message.
```ts type-equiv
type WebProviderStatus =
| { readonly available: true }
| { readonly available: false; readonly reason: 'missing-credential' | 'misconfigured' }
```
A provider's `available(): boolean` is a cheap LOCAL check (credential presence, parseable config) and **must not make network calls**. It is an input to execution-time selection, not a health system: `search()`/`fetch()` read it to pick a usable provider, and a selection failure surfaces as the structured `WebError` the caller routes on — which carries the branchable detail (the missing id or ambiguous candidate set) in its code and message.
Selection never depends on registration, config, or HMR order: a capability has an explicit provider id (config `searchProvider`/`fetchProvider`, or the matching env var feeding the same field), or auto-selects when exactly one usable provider is registered; multiple usable providers with no configured id is `WEB_PROVIDER_AMBIGUOUS`, not first-wins.

View File

@@ -20,7 +20,6 @@ Generated by `pnpm run gen-rfc-index` from the RFC tree — never edit by hand;
|---|---|
| [Unify the agent id and the session id](proposed/simplification/2026-06-20-unify-agent-and-session-id.md) | 2026-06-20 |
| [Prune dead public and result surface](proposed/simplification/2026-07-04-prune-dead-core-spine-surface.md) | 2026-07-04 |
| [Prune unused web seam fields](proposed/simplification/2026-07-12-prune-unused-web-seam-fields.md) | 2026-07-12 |
| [Simplify session-log representation](proposed/simplification/2026-07-12-simplify-session-log-representation.md) | 2026-07-12 |
### Architecture
@@ -104,6 +103,7 @@ Generated by `pnpm run gen-rfc-index` from the RFC tree — never edit by hand;
| [Tighten the hook-protocol contract — dialect, discarded fields, double defaults, and lib-owned `hook/result` semantics](implemented/simplification/2026-07-04-tighten-hook-protocol-contract.md) | 2026-07-04 |
| [Trim unreachable ACP bridge surface — the branding knobs and the kind-sniffing fallback](implemented/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.md) | 2026-07-04 |
| [Drop unconsumed skill provider events](implemented/simplification/2026-07-12-drop-unconsumed-skill-provider-events.md) | 2026-07-12 |
| [Prune unused web seam fields](implemented/simplification/2026-07-12-prune-unused-web-seam-fields.md) | 2026-07-12 |
### Architecture

View File

@@ -64,7 +64,7 @@ flowchart LR
toolWeb -->|ctx.tools.register| webFetch["tool: web_fetch"]
```
`@deepseek-ai/dsh-web` depends only on Cordis and low-level harness support. It declares `ctx.web`, provider interfaces, request/result types, the provider status type, and error codes. It does not import tool, agent, session, LLM, or provider packages.
`@deepseek-ai/dsh-web` depends only on Cordis and low-level harness support. It declares `ctx.web`, provider interfaces, request/result types, the provider availability contract, and error codes. It does not import tool, agent, session, LLM, or provider packages.
Provider packages depend only on `dsh-web` and Cordis. They own credentials, endpoints, wire mapping, parsing, and `WebError` translation, using platform `fetch`. Each provider injects the shared service and registers a backend; only `dsh-web` owns the `ctx.web` key. Provider-private protocol shapes do not create dependencies on `ctx.llm` or a Cordis HTTP service.
@@ -77,52 +77,42 @@ Provider packages depend only on `dsh-web` and Cordis. They own credentials, end
```ts
interface WebSearchProvider {
readonly id: string
status(): WebProviderStatus
search(request: WebSearchRequest, exec?: WebExecContext): Promise<WebSearchResult>
available(): boolean
search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>
}
interface WebFetchProvider {
readonly id: string
status(): WebProviderStatus
fetch(request: WebFetchRequest, exec?: WebExecContext): Promise<WebFetchResult>
available(): boolean
fetch(request: WebFetchRequest, signal?: AbortSignal): Promise<WebFetchResult>
}
interface WebService {
registerSearchProvider(provider: WebSearchProvider): () => void
registerFetchProvider(provider: WebFetchProvider): () => void
search(request: WebSearchRequest, exec?: WebExecContext): Promise<WebSearchResult>
fetch(request: WebFetchRequest, exec?: WebExecContext): Promise<WebFetchResult>
}
interface WebExecContext {
readonly signal?: AbortSignal
search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>
fetch(request: WebFetchRequest, signal?: AbortSignal): Promise<WebFetchResult>
}
```
`WebExecContext` is execution control, not business input. It carries only `signal`, so `tool-web` propagates turn cancellation, tool timeout, and agent disposal into provider network requests, SSE readers, and expensive decoding. It does not pass `ToolExecution` through the seam — that would make `dsh-web` depend on `dsh-tools`.
The optional signal is execution control, not business input: `tool-web` passes `exec.signal` directly so turn cancellation, tool timeout, and agent disposal reach provider network requests, stream readers, and expensive decoding. The seam does not pass `ToolExecution` through — that would make `dsh-web` depend on `dsh-tools`.
Provider ids are stable strings and unique within their capability kind. Registering a duplicate search provider id or duplicate fetch provider id fails rather than silently replacing the old provider. Provider registration returns a disposer and follows the existing `ctx.tools.register()` / `ctx.systemPrompt.section()` pattern: the mutation is wrapped in `ctx.effect()` so the registration is torn down with the contributing fiber.
## Provider status and selection
## Provider availability and selection
Provider status and capability selection are separate concepts, but both stay minimal. A provider reports only whether that concrete implementation is usable by cheap local checks such as credential presence or parseable endpoint config. A provider `status()` must not make network calls.
Provider availability and capability selection are separate concepts, but both stay minimal. A provider reports only whether that concrete implementation is usable by cheap local checks such as credential presence or parseable endpoint config. A provider `available()` must not make network calls.
`LlmService` has no status type at all: availability is expressed as registry membership plus a resolution-time throw. `ctx.web` follows the same discipline. The seam exposes no aggregated capability-status query — `search()` / `fetch()` derive the selection on each call from the configured provider id, the registered providers, and each provider's cheap local `status()`, and a selection failure is the structured `WebError` thrown at execution time, whose code answers "in which broad category does this capability fail" and whose message answers "exactly which provider/ids/reason." A caller that needs to know whether a capability can run executes and routes that error; nothing is stored as mutable service state.
`LlmService` has no status type at all: availability is expressed as registry membership plus a resolution-time throw. `ctx.web` follows the same discipline. The seam exposes no aggregated capability-status query — `search()` / `fetch()` derive the selection on each call from the configured provider id, the registered providers, and each provider's cheap local `available()` boolean, and a selection failure is the structured `WebError` thrown at execution time. A caller that needs to know whether a capability can run executes and routes that error; nothing is stored as mutable service state.
`WebProviderStatus` is an input to selection, not a health system. `tool-web` never calls a provider's `status()` directly — its only path into the seam is `search()` / `fetch()` — so selection policy has one owner.
```ts
type WebProviderStatus =
| { readonly available: true }
| { readonly available: false; readonly reason: 'missing-credential' | 'misconfigured' }
```
The boolean is an input to selection, not a health system. `tool-web` never calls a provider's `available()` directly — its only path into the seam is `search()` / `fetch()` — so selection policy has one owner.
Selection must not depend on registration order. Cordis load order, config ordering, and HMR timing are not product semantics.
| Situation | Execution behavior |
|---|---|
| A configured provider id is registered and `status().available === true` | runs that provider |
| A configured provider id is registered and `available() === true` | runs that provider |
| A configured provider id is not registered | fails with `WEB_PROVIDER_CONFIGURED_MISSING` |
| A configured provider id is registered but unavailable | fails with `WEB_PROVIDER_CONFIGURED_UNAVAILABLE` |
| No provider id is configured and exactly one provider for that kind is registered and available | runs that single provider |
@@ -184,8 +174,6 @@ interface WebSearchRequest {
}
interface WebSearchResult {
readonly providerId: string
readonly query: string
readonly content?: string
readonly sources: readonly WebSearchSource[]
readonly truncated: boolean
@@ -212,20 +200,17 @@ The `web_fetch` implementation is an anonymous public HTTP(S) fetch provider, `l
The seam request stays smaller than OpenCode's model-facing tool:
- `url`: required HTTP(S) URL.
- `timeoutMs`: optional positive number capped by the provider.
The seam request deliberately does not include `format`, `prompt`, or provider-specific extraction controls. `format` is a presentation decision over a fetched resource; `prompt` is a higher-level LLM summarization instruction; extraction APIs such as Firecrawl, Exa, Tavily, or Parallel may not expose a concrete HTTP response. If the product later needs provider-backed page extraction, that is a separate `web_extract` capability or a deliberate widening of this seam — extract semantics are never smuggled into `web_fetch` by making every HTTP field optional.
The seam request deliberately does not include a per-call timeout, `format`, `prompt`, or provider-specific extraction controls. Cancellation is the direct optional execution signal, while the fetch provider owns one deployment-configured timeout backstop. `format` is a presentation decision over a fetched resource; `prompt` is a higher-level LLM summarization instruction; extraction APIs such as Firecrawl, Exa, Tavily, or Parallel may not expose a concrete HTTP response. If the product later needs provider-backed page extraction, that is a separate `web_extract` capability or a deliberate widening of this seam — extract semantics are never smuggled into `web_fetch` by making every HTTP field optional.
HTTP status is part of the fetched resource state, not automatically a tool failure. A successful network fetch of a `404` or `500` response returns `WebFetchResult` with the status code and a bounded decoded body when the content type is supported. `WebError` is for failures to safely retrieve or represent the resource: invalid or blocked URL, redirect policy violation, timeout, abort, response too large, unsupported content type, provider failure, or network failure.
```ts
interface WebFetchRequest {
readonly url: string
readonly timeoutMs?: number
}
interface WebFetchResult {
readonly providerId: string
readonly url: string
readonly statusCode: number
readonly body: WebFetchBody
@@ -257,11 +242,11 @@ SSRF / private-network protection (blocking private, loopback, link-local, multi
`dsh-tool-web` owns two `ToolDefinition`s: `web_search` and `web_fetch`. It owns model-facing JSON schemas, snake_case argument names, prompt sections, result rendering to `ContentBlock[]`, `presentCall`, and `presentResult`.
`dsh-tool-web` must not enumerate providers or call provider `status()` directly. Its only path into the seam is `ctx.web.search()` / `ctx.web.fetch()`. That keeps provider selection in one layer; otherwise the tool package could decide one provider is usable while execution resolves a different state.
`dsh-tool-web` must not enumerate providers or call provider `available()` directly. Its only path into the seam is `ctx.web.search()` / `ctx.web.fetch()`. That keeps provider selection in one layer; otherwise the tool package could decide one provider is usable while execution resolves a different state.
Tool registration is a minimal stable sync: on plugin startup the `dsh-tool-web` `Config` (`search?: boolean`, `fetch?: boolean`, both default `true`) enables or disables each web tool; an enabled tool is registered with a fiber-scoped disposer via the effect-based registry; neither tool is disposed merely because its selected provider is missing, unusable, or ambiguous; disposing the `tool-web` fiber tears down its registrations automatically.
Provider status changes affect execution results and diagnostics, not whether the model-facing schema exists. If a product wants no web tools at all, it disables `dsh-tool-web` or the individual web tool in config; if it wants web tools but the backend is misconfigured, the model sees a structured tool error at execution time.
Provider availability changes affect execution results and diagnostics, not whether the model-facing schema exists. If a product wants no web tools at all, it disables `dsh-tool-web` or the individual web tool in config; if it wants web tools but the backend is misconfigured, the model sees a structured tool error at execution time.
The prompt guidance explains the semantic split — `web_search` for discovery and current information, `web_fetch` when the model needs the content of a specific URL — and the prompt and tool result tell the model to cite relevant URLs with markdown links.

View File

@@ -74,7 +74,7 @@ No new session event is needed for reconstructability: `TOOL_TIMEOUT` is the fin
`web_fetch` and `web_search` are migrated. `dsh-tool-web` keeps ownership of their model-facing schemas, and those schemas expose no timeout knob: `web_fetch` dropped its `timeout_ms` parameter to match the reference-agent shape, and `web_search` stays query-only. The tool bodies do not import `@deepseek-ai/dsh-timeout`; they forward `exec.signal` to `ctx.web`.
`dsh-web-fetch-local` keeps a provider-level timeout (`timeoutMs`/`maxTimeoutMs`) as a large resource backstop for direct `ctx.web.fetch()` callers and misconfigured deployments; it owns no model-facing timeout. When a `TOOL_TIMEOUT` signal reaches the fetch provider first, provider-scoped classification treats it as upstream `WEB_ABORTED`, and the outer `tools/execute` wrapper replaces the final tool result with `TOOL_TIMEOUT`. A shipped web-tool deployment configures the provider backstop above the `timeout-policy` budget so the tool-call policy normally wins for model calls.
`dsh-web-fetch-local` keeps one configured provider-level `timeoutMs` as a large resource backstop for direct `ctx.web.fetch()` callers and misconfigured deployments; it owns no model-facing timeout. When a `TOOL_TIMEOUT` signal reaches the fetch provider first, provider-scoped classification treats it as upstream `WEB_ABORTED`, and the outer `tools/execute` wrapper replaces the final tool result with `TOOL_TIMEOUT`. A shipped web-tool deployment configures the provider backstop above the `timeout-policy` budget so the tool-call policy normally wins for model calls.
`bash` stays on the current backend timeout path. `dsh-tool-bash` continues to expose `timeoutMs` and `run_in_background`; `dsh-bash-local` continues to use `@deepseek-ai/dsh-timeout` for `BASH_TIMEOUT`; hook bridges continue to call `runHook()` and pass `timeoutMs` through `ctx.bash`. This keeps foreground/background/hook behavior stable.

View File

@@ -1,6 +1,6 @@
# RFC: Prune unused web seam fields
Status: proposed
Status: implemented
## Problem
@@ -8,23 +8,18 @@ The web capability carries request/result/status values that every shipped imple
`WebFetchRequest.timeoutMs` is likewise never set by a production caller. `tool-web` supplies only the URL, uses the tool definition's timeout plus `exec.signal` for the caller deadline, and relies on the local provider's configured default as a backstop. The unused per-request override forces `web-fetch-local` to expose `maxTimeoutMs`, clamp two timeout sources, and document/test precedence no product path can select. `WebExecContext` is another one-field wrapper: every caller allocates `{ signal }` and every provider immediately unwraps `exec?.signal`; no second execution-control field exists.
## Proposal
## Decision
Remove the search/fetch `providerId` result echoes and search `query` echo; callers already own the request and provider selection. Shrink provider status to availability alone, preferably a boolean-returning method if that produces the clearest seam. Remove per-request fetch timeout, `maxTimeoutMs`, and their clamp/validation branches while retaining the provider's configurable default timeout and tool-level deadline. Replace `WebExecContext` with a direct optional `AbortSignal` parameter.
The web seam omits the search/fetch `providerId` result echoes and search `query` echo; callers already own the request and provider selection. Providers expose availability as a boolean-returning method. Fetch requests have no per-request timeout or `maxTimeoutMs` clamp; the local provider retains its configurable default timeout and the tool retains its own deadline. Provider methods receive a direct optional `AbortSignal` instead of a one-field `WebExecContext` wrapper.
Update all web implementations, the model-facing tool, package READMEs/JSDoc, type-equivalence records, and tests. Keep the interface/implementation/consumer package split, provider selection, source citations, final-URL/status data, truncation reporting, and all safety limits.
All web implementations and the model-facing tool use the smaller contract. The interface/implementation/consumer package split, provider selection, source citations, final-URL/status data, truncation reporting, and safety limits remain.
## Alternatives considered
**Keep self-describing results, per-request deadlines, and an extensible execution-context object.** Result echoes can help generic telemetry, a request timeout can help trusted programmatic callers, and the wrapper leaves room for future controls. No such consumer/second field exists; carrying duplicate identity, a second deadline policy, and wrap/unwrap plumbing through every provider makes the current contract harder to implement and explain. If telemetry or per-call budget control arrives, it should define which deadline wins, where provider identity is observed, and whether multiple controls justify a context object.
## Acceptance criteria
## Consequences
- Every retained web request/result/status field has a production reader or is required to execute the provider request.
- Tool-visible search/fetch output, provider fallback, abort behavior, configured timeout backstop, truncation, and citations remain covered.
- No `maxTimeoutMs`, request-timeout precedence branch, or one-field execution-context wrapper remains.
- Typecheck, coverage, snapshots, doc-sync, module-graph verification, build, and hygiene pass.
## Risks
Every retained web request/result field is consumed by production code or required to execute the provider request. Tool-visible search/fetch output, provider fallback, abort behavior, the configured timeout backstop, truncation, and citations remain covered without a request-timeout precedence branch or execution-context wrapper.
Pre-release programmatic callers lose result provenance echoes and per-request fetch deadlines. The provider still has a deployment-configurable timeout and respects cancellation, so the simplification removes configurability rather than a safety bound.