Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions .scratch/deepseek-search-provider/implementation-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# DeepSeek search provider implementation report

## Scope

- Repository: `guionai/web`
- Branch: `deepseek-search-provider`
- Fixed point: `987f664b1d43d2b3325849a8ed818c7332bf8808`
- Implementation commit: `6826d3ead70eafaca21965b125041dc95ef63a7a` (`feat(search): add explicit DeepSeek provider`)
- Delivery boundary: the complete `deepseek-search-provider` spec and tickets
01, 02, and 03. Code review and deployment were excluded.

The tickets were implemented in dependency order: the shared Core adapter and
selection seam first, then CLI/MCP/Pi and DSH, followed by the server-local
HTTP selection and OpenAPI contract.

## Review repair batch

- Repair commit: `e7ef2fc` (`fix(search): tighten DeepSeek review contracts`).
- The review-again gate classified these as local contract/test repairs: the
runtime method and risk surface are unchanged, so focused verification was
sufficient and a second broad code review was not required.
- Removed the three DeepSeek packed-artifact source-string assertions from the
artifact test. Existing runtime, provider, and rendered-settings tests remain
the behavior coverage for those contracts.
- DeepSeek's seven endpoint/model/version/token/tool protocol constants are now
implementation-private in web-core. The Core fixture asserts the expected
protocol through test-local constants instead of importing production
implementation details.
- DSH alpha.3 deployed-entrypoint acceptance was unavailable on this host. The
documented path `/home/neil/.local/share/dsh-runtime/node_modules/@deepseek-ai/dsh/lib/bin.js`
failed the availability check (`test -f` returned `unavailable`), and the
direct probe `node --expose-internals /home/neil/.local/share/dsh-runtime/node_modules/@deepseek-ai/dsh/lib/bin.js --help`
failed with `MODULE_NOT_FOUND`. Consequently, no Host/browser acceptance was
run, and no host state or credentials were created or modified.

## Ticket outcomes

### 01 — Add explicit DeepSeek search outside DSH

- Core recognizes `deepseek` only when explicitly selected and requires
`DEEPSEEK_API_KEY`; Exa/Brave implicit selection remains unchanged, so a
DeepSeek key alone never changes the default.
- The adapter posts one fixed Anthropic-compatible Messages request to
`https://api.deepseek.com/anthropic/v1/messages` using
`deepseek-v4-flash`, `max_tokens: 4096`, the fixed auxiliary search prompt,
and `web_search_20250305` with `max_uses: 5`. The endpoint/model/tool details
are not host request fields.
- Only structured `web_search_tool_result` / `web_search_result` blocks are
mapped. URL-keyed `cited_text` excerpts become snippets, duplicate URLs are
removed, and prose-only responses are provider errors. Requests use the
existing bounded timeout/cancellation and secret-safe response handling.
- CLI and MCP flags, Pi's existing provider environment mechanism, runtime
credentials, and MCP error redaction recognize DeepSeek. Their search inputs
remain provider-neutral where applicable.
- README and host documentation describe explicit selection, normalized
results, and the one auxiliary model-call cost.

### 02 — Add DeepSeek to DSH provider settings

- DSH's live provider union/picker includes `deepseek` and its label.
- The settings client manages the namespaced write-only
`GUIONAI_DSH_WEB_DEEPSEEK_API_KEY` credential alongside Exa and Brave. The
UI has no DeepSeek endpoint field; the existing endpoint control remains
explicitly a Kepos Bridge setting.
- The host adapter resolves only the selected DeepSeek credential for each
operation and forwards `provider: "deepseek"` through Core. Kepos-only tool
registration and non-DeepSeek provider behavior remain unchanged.
- DSH host, browser-client, packed-artifact, settings, and provider-fake tests
cover selection, DeepSeek metadata-only status, the existing write/remove
flow, and artifact presence without live credentials.
- The DSH README and package metadata document the picker/key workflow, no
DeepSeek endpoint input, normalized results, and call cost.

### 03 — Select DeepSeek for the personal HTTP service

- With no `WEB_SEARCH_PROVIDER`, HTTP retains its Bridge-first policy and one
Exa retry, including the existing Exa startup requirement.
- With server-local `WEB_SEARCH_PROVIDER=deepseek`, startup requires a
non-empty `DEEPSEEK_API_KEY`; each request calls DeepSeek exactly once and
never falls back to Bridge or Exa. HTTP clients still send only
`{ "query": "..." }`.
- HTTP validation and generated OpenAPI include the `DeepSeek` response label
without adding a request provider field. Fakes cover selected success,
failure/no-fallback, startup key validation, cancellation treatment, and
unchanged default behavior.
- The HTTP reference, README, glossary, and ADR document the server-local
selection, credential boundary, normalized contract, and no-fallback rule.

## Verification

All implementation and review-repair checks completed successfully:

- Focused `pnpm exec vitest run packages/web-core/test/search.test.ts
packages/dsh-web/test/artifact.test.ts` — 2 files and 14 tests passed.

- `pnpm format:check` and explicit Prettier checks for the changed Markdown
references; `git diff --check`.
- `pnpm typecheck`.
- `pnpm test` — 19 test files and 132 tests passed. The existing missing DSH
primitive source-map warning was non-fatal.
- `pnpm build` — all four workspace packages built and generated
`packages/web/dist/openapi.yaml` with the DeepSeek response enum.
- `pnpm test:release` — release version synchronization fixtures passed.
- `pnpm test:pack` — packed-installation/host-loading checks passed for
`@guionai/web`, `@guionai/pi-web`, and `@guionai/dsh-web`.

No live DeepSeek service, credentials, production services, or persistent host
state were used.

## Changed paths and size

Against the fixed point, excluding generated `dist` output, lockfiles, and
this report:

- Product code: 209 additions and 12 deletions (221 changed lines) across
Core, CLI, MCP, Pi, DSH, and HTTP.
- Tests: 372 additions and 2 deletions (374 changed lines), including Core
request/mapping/error seams, adapter forwarding, DSH credential/UI checks,
HTTP selection, and OpenAPI assertions.
- Documentation/configuration: 71 additions and 37 deletions (108 changed
lines), including README, HTTP reference, glossary, ADR, DSH README, and
package metadata.
- Total: 652 additions and 51 deletions (703 changed lines), within the spec's
580–970 total-line estimate. Product code is below its 260–410 estimate
because the implementation reuses the existing bounded-request, normalized
result, host-selection, settings, and artifact seams; the tests and
documentation remain within their estimated ranges.

## Remaining concerns

- A live DeepSeek capability/account probe remains intentionally deferred by
the spec; tests use injected local fetch fixtures only.
- Public or multi-tenant HTTP hardening remains out of scope as documented by
ADR 0001 and the existing deferred security note.
- A second broad code review and deployment were intentionally not run; the
review-again gate classified this batch as local repairs.

## Acceptance result

Tickets 01, 02, and 03 and the complete `deepseek-search-provider` spec are
implemented and verified.
14 changes: 10 additions & 4 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ One of Guion Web's read-only capabilities: Search, Fetch, Links, Documentation R
_Avoid_: Tool, endpoint

**Search**:
The general web-retrieval Research Operation. In the Personal Web Service, it uses Kepos Bridge first and transparently falls back to Exa only when the Bridge is unavailable; its response identifies the provider that supplied results.
_Avoid_: Bridge search, provider-selected search
The general web-retrieval Research Operation. CLI, MCP, Pi, and DSH can
explicitly select Exa, Brave, DeepSeek, or Kepos Bridge. In the Personal Web
Service, it uses Kepos Bridge first and transparently falls back to Exa only
when the Bridge is unavailable unless the operator sets
`WEB_SEARCH_PROVIDER=deepseek`; that server-local mode calls DeepSeek only and
has no fallback. Its response identifies the provider that supplied results.
_Avoid_: Bridge search, HTTP provider parameter

**Bridge Data Operation**:
A typed, Bridge-only lookup for weather, sports, finance, or time. It has no Exa fallback because Exa search is not an equivalent result source.
Expand Down Expand Up @@ -45,8 +50,9 @@ _Avoid_: Checked-in OpenAPI file, independently versioned schema

**HTTP Service**:
The Hono-based `/v1` JSON API shipped by `web serve` and the GHCR image. It
uses server-local credentials and Bridge Route configuration; clients do not
select providers or submit a generic Bridge command. Its page-reading routes use
uses server-local credentials, Bridge Route, and optional DeepSeek provider
configuration; clients do not select providers or submit a generic Bridge
command. Its page-reading routes use
the same `render: "http" | "browser"`, `full`, and `section_id` contract; the
browser executable name appears only in operator setup.
_Avoid_: Remote MCP, public service
63 changes: 41 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Guion Web

Guion Web is a Node.js web research toolkit. It provides Exa, Brave, or a
managed Kepos Bridge search endpoint,
Guion Web is a Node.js web research toolkit. It provides Exa, Brave, DeepSeek,
or a managed Kepos Bridge search endpoint,
Context7 library documentation lookup, Sourcegraph public code search, page-link
discovery, and two page-rendering modes through a CLI, stdio MCP server, personal
HTTP service, Pi extension, and DeepSeek Harness (DSH) integration: HTTP
Expand All @@ -21,28 +21,37 @@ npm install --global @guionai/web
npx @guionai/web --help
```

Search needs one provider credential for Exa or Brave. If both are present, Exa
is selected by default; select a provider explicitly with `--provider exa`,
`--provider brave`, or `--provider kepos-bridge`. Kepos Bridge uses the bundled
default route unless it runs in DSH, whose live settings card can override the
route.
Search needs one provider credential for Exa, Brave, or DeepSeek. If Exa and
Brave are both present, Exa is selected by default; DeepSeek is never selected
implicitly. Select a provider explicitly with `--provider exa`,
`--provider brave`, `--provider deepseek`, or `--provider kepos-bridge`.
DeepSeek makes one auxiliary model call using its native web-search tool and
returns the same normalized ranked URL/title/snippet results as the other
providers. Kepos Bridge uses the bundled default route unless it runs in DSH,
whose live settings card can override the route.
Context7 works anonymously when its key is absent.

The HTTP service always requires a non-empty `EXA_API_KEY`: it tries the
server-local Kepos Bridge route first and retries Exa once when Bridge fails.
HTTP clients cannot select a provider, pass credentials, or override the Bridge
route per request. Set `KEPOS_BRIDGE_ENDPOINT` to replace the default route
The HTTP service always uses the Bridge-to-Exa policy by default and requires a
non-empty `EXA_API_KEY` for its retry. Set the server-local
`WEB_SEARCH_PROVIDER=deepseek` to select DeepSeek instead; this requires a
non-empty `DEEPSEEK_API_KEY` and does not fall back to Bridge or Exa when the
DeepSeek request fails. HTTP clients cannot select a provider, pass credentials,
or override the Bridge route per request. Set `KEPOS_BRIDGE_ENDPOINT` to replace the default route
(`http://codex-bridge.localhost:17480/codex/web-search`); it must be a complete
HTTP(S) URL without credentials, query, or fragment.

```bash
export EXA_API_KEY="..."
# or
export BRAVE_API_KEY="..."
# for explicit DeepSeek selection in CLI, MCP, Pi, or DSH
export DEEPSEEK_API_KEY="..."
# optional, for authenticated Context7 requests
export CONTEXT7_API_KEY="..."
# optional complete Bridge route for `web serve`
export KEPOS_BRIDGE_ENDPOINT="http://127.0.0.1:8787/codex/web-search"
# HTTP/Pi: select DeepSeek server-side (HTTP clients still send {"query":"..."})
export WEB_SEARCH_PROVIDER="deepseek"
```

Do not put credentials in command arguments or commit them. The CLI reads these
Expand All @@ -51,7 +60,9 @@ application configuration path.

## Personal HTTP service

Run the service with the server-local environment above:
Run the service with the server-local environment above. Leave
`WEB_SEARCH_PROVIDER` unset for Bridge-to-Exa; set it to `deepseek` for the
DeepSeek-only path:

```bash
web serve --host 0.0.0.0 --port 8787
Expand All @@ -65,16 +76,19 @@ docker run --rm -p 8787:8787 \
Every HTTP operation is a versioned JSON `POST` route. Request and response schemas
are generated into `openapi.yaml` from the same route definitions:

| Route | Request | Purpose |
| ------------ | --------------------------------------------------------- | -------------------------------------------------------- |
| `/v1/search` | `{ "query": "..." }` | Kepos Bridge search with one Exa retry on Bridge failure |
| `/v1/fetch` | `{ "url", "section_id?", "full?", "render?", "waitMs?" }` | Fetch Markdown |
| `/v1/links` | `{ "url", "limit?", "render?", "waitMs?" }` | List page HTTP(S) links |
| Route | Request | Purpose |
| ------------ | --------------------------------------------------------- | --------------------------------------------------------------- |
| `/v1/search` | `{ "query": "..." }` | Server-selected search: Bridge→Exa by default, or DeepSeek only |
| `/v1/fetch` | `{ "url", "section_id?", "full?", "render?", "waitMs?" }` | Fetch Markdown |
| `/v1/links` | `{ "url", "limit?", "render?", "waitMs?" }` | List page HTTP(S) links |

The complete human-readable contract is in the [HTTP service reference](docs/http-service.md).

Search keeps a successful empty Bridge result, retries Exa exactly once for a
non-cancellation Bridge failure, and reports the provider in its response.
non-cancellation Bridge failure when no server provider is selected, and
reports the provider in its response. DeepSeek selection is server-local and
has no automatic fallback. The request remains `{ "query": "..." }` in every
case.
Weather, sports, finance, and time are not exposed because the configured
providers do not offer contract-equivalent official typed data APIs. Invalid JSON
bodies, unknown fields, and invalid typed values are rejected before an upstream call.
Expand Down Expand Up @@ -104,6 +118,7 @@ document on stdout, which is useful for automation.

```bash
web search --provider exa -- "Node AbortSignal"
web search --provider deepseek -- "Node AbortSignal"
web search --provider kepos-bridge -- "Node AbortSignal"
web fetch https://example.com/article
web fetch https://example.com/article --section introduction
Expand All @@ -129,6 +144,7 @@ Run the stdio server with the same credential environment:
web mcp
# Pin search selection for the lifetime of this MCP process:
web mcp --provider brave
web mcp --provider deepseek
web mcp --provider kepos-bridge
```

Expand All @@ -155,8 +171,10 @@ an integer `waitMs` when its host provides that optional executable.
from the original page DOM.

Set `WEB_SEARCH_PROVIDER=kepos-bridge` before starting Pi to select the
credential-free Kepos Bridge provider. Pi uses the bundled default Bridge route;
only DSH exposes a route setting.
credential-free Kepos Bridge provider, or `WEB_SEARCH_PROVIDER=deepseek` with
`DEEPSEEK_API_KEY` for explicit DeepSeek search. Pi uses the bundled default
Bridge route; only DSH exposes a route setting. DeepSeek is never selected by
the presence of its key alone.

## DSH

Expand All @@ -167,10 +185,11 @@ dsh plugin --profile web add @guionai/dsh-web
```

The included profile patch routes stock PTC web search through the selected Exa,
Brave, or Kepos Bridge provider. Its settings UI stores provider selection and
Brave, DeepSeek, or Kepos Bridge provider. Its settings UI stores provider selection and
the complete non-secret Kepos Bridge route (default
`http://codex-bridge.localhost:17480/codex/web-search`) and manages namespaced write-only
credentials. Selecting Kepos Bridge additionally exposes `web_weather`,
credentials, including a write-only DeepSeek API key. DeepSeek uses the same
provider picker/key workflow and exposes no DeepSeek endpoint field. Selecting Kepos Bridge additionally exposes `web_weather`,
`web_sports`, `web_finance`, and `web_time`; these tools are removed when another
provider is selected. Fetch, link discovery, documentation, and Sourcegraph tools
also run in-process. The host DSH packages and React are peers supplied by DSH.
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0001-containerized-rest-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Guion Web will add a self-hosted, single-user HTTP service in a portable contain

## Consequences

The service's provider credentials and Bridge Route are server-local configuration; clients cannot choose a provider or supply a Bridge Route per request. Search tries Kepos Bridge and uses Exa only when the Bridge is operationally unavailable; it does not fall back for cancellation, malformed client input, or an empty result set. A failed Bridge attempt is retried through Exa exactly once, and a successful empty Bridge response is returned unchanged. Typed Bridge operations are intentionally not exposed: Exa has no equivalent official weather, sports, or time API, and its premium finance integration is not contract-compatible. Fetch and Links use `render: "http"` by default or explicit `render: "browser"` with a required `waitMs`; the operator-installed executable remains an implementation detail. The shared page-reading module owns automatic navigation trees, complete `full` extraction, and `section_id` continuation. `openapi.yaml` is generated from the release build rather than manually maintained or independently versioned; the standalone [HTTP service reference](../http-service.md) is its human-readable companion. Public or multi-tenant deployment hardening is deliberately deferred in `.scratch/defered/public-http-service-security.md`.
The service's provider credentials, Bridge Route, and optional `WEB_SEARCH_PROVIDER=deepseek` selection are server-local configuration; clients cannot choose a provider or supply a Bridge Route per request. With no provider selection, search tries Kepos Bridge and uses Exa only when the Bridge is operationally unavailable; it does not fall back for cancellation, malformed client input, or an empty result set. A failed Bridge attempt is retried through Exa exactly once, and a successful empty Bridge response is returned unchanged. When DeepSeek is selected, the service calls only DeepSeek and never falls back. DeepSeek performs one auxiliary model call internally and returns the same normalized result contract; its Messages/tool protocol is not exposed to HTTP callers. Typed Bridge operations are intentionally not exposed: Exa has no equivalent official weather, sports, or time API, and its premium finance integration is not contract-compatible. Fetch and Links use `render: "http"` by default or explicit `render: "browser"` with a required `waitMs`; the operator-installed executable remains an implementation detail. The shared page-reading module owns automatic navigation trees, complete `full` extraction, and `section_id` continuation. `openapi.yaml` is generated from the release build rather than manually maintained or independently versioned; the standalone [HTTP service reference](../http-service.md) is its human-readable companion. Public or multi-tenant deployment hardening is deliberately deferred in `.scratch/defered/public-http-service-security.md`.
Loading
Loading